DataTable
Типизированная таблица с локальной или серверной обработкой данных. Сортировка выполняется из заголовков, а фильтры открываются в отдельном доступном диалоге и не занимают место над таблицей.
Полный пример
| Статус | ||||
|---|---|---|---|---|
| Анна Петрова | Design | Активен | 28 | 7 |
| Борис Волков | Frontend | Активен | 34 | 12 |
| Виктор Орлов | Backend | В отпуске | 23 | 4 |
| Галина Мирова | Frontend | Активен | 31 | 9 |
sort, filters и page — controlled state. Таблица отправляет обновления через v-model, а родитель хранит состояние. При изменении сортировки или фильтров страница автоматически сбрасывается на первую.
Ленивая загрузка
lazy отключает обычную pagination и показывает уже загруженные строки. Когда пользователь приближается к нижней границе, компонент отправляет load-more с текущим offset, сортировкой и фильтрами.
| ID | Команда | Возраст | |
|---|---|---|---|
| 1 | Пользователь 1 | Design | 20 |
| 2 | Пользователь 2 | Frontend | 21 |
| 3 | Пользователь 3 | Backend | 22 |
| 4 | Пользователь 4 | QA | 23 |
| 5 | Пользователь 5 | Design | 24 |
| 6 | Пользователь 6 | Frontend | 25 |
| 7 | Пользователь 7 | Backend | 26 |
| 8 | Пользователь 8 | QA | 27 |
<UiDataTable
:rows="items"
:columns="columns"
lazy
:has-more="response.hasNextPage"
:loading-more="loadingMore"
:max-height="480"
@load-more="({ offset, sort, filters }) =>
fetchNextPage({ offset, sort, filters })"
/>hasMore=falseостанавливает автоматические запросы.loadingMoreблокирует повторный запрос, но не скрывает текущие строки.lazyThresholdзадаёт расстояние до нижней границы в пикселях.- Кнопка внизу остаётся доступным fallback для клавиатуры и повторной загрузки.
- Слот
loading-moreполностью заменяет состояние догрузки.
Закреплённые колонки
Укажите sticky: 'start' или sticky: 'end' в описании колонки. Таблица учитывает LTR/RTL и использует logical CSS properties.
| Сотрудник | Команда | Статус | Возраст | Проекты | Город | ||
|---|---|---|---|---|---|---|---|
| Анна Петрова | Design | Активен | 28 | 7 | анна.петрова@example.com | Казань | |
| Борис Волков | Frontend | Активен | 34 | 12 | борис.волков@example.com | Тбилиси | |
| Виктор Орлов | Backend | В отпуске | 23 | 4 | виктор.орлов@example.com | Москва | |
| Галина Мирова | Frontend | Активен | 31 | 9 | галина.мирова@example.com | Казань | |
| Денис Левин | Design | Неактивен | 39 | 15 | денис.левин@example.com | Тбилиси | |
| Елена Соколова | Backend | Активен | 26 | 6 | елена.соколова@example.com | Москва | |
| Жанна Белова | QA | В отпуске | 30 | 8 | жанна.белова@example.com | Казань |
Если закреплено несколько колонок с одной стороны, задайте накопительный stickyOffset:
[
{ key: 'select', label: '', sticky: 'start', width: 48 },
{
key: 'name',
label: 'Название',
sticky: 'start',
stickyOffset: 48,
width: 220,
},
]Для предсказуемой широкой таблицы используйте tableMinWidth, фиксированные width колонок и при необходимости tableLayout="fixed". truncate: true добавляет ellipsis длинному содержимому.
Плотность, границы и строки
| Статус | ||||
|---|---|---|---|---|
| Анна Петрова | Design | active | 28 | 7 |
| Борис Волков | Frontend | active | 34 | 12 |
| Виктор Орлов | Backend | vacation | 23 | 4 |
| Галина Мирова | Frontend | active | 31 | 9 |
| Денис Левин | Design | inactive | 39 | 15 |
| Елена Соколова | Backend | active | 26 | 6 |
| Жанна Белова | QA | vacation | 30 | 8 |
denseуменьшает вертикальные и горизонтальные отступы.borderedдобавляет вертикальные разделители.stripedчередует фон строк.hoverableуправляет hover-подсветкой.rowClassпринимает строку или функцию(row, index) => className.
Описание колонок
import type { UiDataTableColumn } from '@neytron/compact-ui';
interface User {
id: number;
profile: { name: string };
balance: number;
}
const columns: UiDataTableColumn<User>[] = [
{
key: 'name',
label: 'Пользователь',
accessor: (row) => row.profile.name,
sortable: true,
minWidth: 220,
filter: {
type: 'text',
placeholder: 'Начните вводить имя',
},
},
{
key: 'balance',
label: 'Баланс',
align: 'end',
width: 140,
sortable: true,
format: (value) => `${value} ₽`,
comparator: (left, right) => left.balance - right.balance,
filter: {
type: 'number-range',
min: 0,
step: 100,
},
},
];accessor может быть ключом строки или функцией для вложенных и вычисляемых значений. comparator переопределяет стандартное сравнение, format меняет только отображение, а headerClass и cellClass позволяют точечно добавить классы.
Встроенные фильтры
| Тип | Значение | Назначение |
|---|---|---|
text | string | Поиск подстроки, без учёта регистра по умолчанию |
select | primitive или массив | Один или несколько вариантов, опциональный поиск |
number-range | { min?, max? } | Числовой диапазон |
custom | любое | Пользовательский UI и predicate |
Фильтры одной таблицы объединяются через AND. Для любой колонки можно задать свой predicate:
{
key: 'tags',
label: 'Теги',
filter: {
type: 'custom',
predicate: (cellValue, filterValue) =>
cellValue.some((tag) => filterValue.includes(tag)),
},
}Для custom используйте общий слот filter и выберите UI по ключу колонки:
<UiDataTable
v-model:filters="customFilters"
:rows="rows"
:columns="columns"
>
<template #filter="{ column, value, setValue }">
<UiInput
v-if="column.key === 'name'"
:model-value="value ?? ''"
label="Кастомный поиск"
@update:model-value="setValue"
/>
</template>
</UiDataTable>При наличии слота filter он отвечает за отображение всех фильтров. Если нужна полностью отдельная кнопка или собственный toolbar, можно напрямую использовать UiDataTableFilters.
Server-side режим
Каждый этап можно отключить независимо:
<UiDataTable
v-model:sort="query.sort"
v-model:filters="query.filters"
v-model:page="query.page"
:rows="response.items"
:columns="columns"
:total="response.total"
:page-size="25"
:loading="pending"
manual-sorting
manual-filtering
manual-pagination
@sort-change="load"
@filters-change="load"
@page-change="load"
/>manualSortingотключает локальную сортировку.manualFilteringотключает локальные predicates.manualPaginationсчитает, чтоrowsуже содержат нужную страницу.totalзадаёт полное число строк для pagination.- Для cursor/offset API вместо
manualPaginationможно использоватьlazy.
Флаги можно комбинировать: например, оставить локальную сортировку, но загружать фильтры и страницы с API.
Слоты
| Слот | Данные |
|---|---|
toolbar | rows, total, filters, sort |
header-{key} | column, sort, toggleSort |
cell-{key} | row, column, value, rowIndex |
filter | column, value, setValue, filters |
loading | — |
loading-more | loading, loadMore |
empty | filters |
footer | rows, total, page |
События
update:sort,sort-changeupdate:filters,filters-changeupdate:page,page-changerow-clickrow-dblclick
update:* предназначены для v-model, а *-change удобны для загрузки данных с сервера.
Props
| Prop | Тип | Default | Обязательный |
|---|---|---|---|
rows | readonly TRow[] | — | Да |
columns | readonly UiDataTableColumn<TRow>[] | — | Да |
rowKey | UiDataTableRowKey<TRow> | — | Нет |
sort | UiDataTableSort | null | null | Нет |
filters | UiDataTableFilterValues | {} | Нет |
page | number | 1 | Нет |
pageSize | number | 10 | Нет |
total | number | — | Нет |
pagination | boolean | true | Нет |
manualSorting | boolean | false | Нет |
manualFiltering | boolean | false | Нет |
manualPagination | boolean | false | Нет |
loading | boolean | false | Нет |
striped | boolean | false | Нет |
hoverable | boolean | true | Нет |
stickyHeader | boolean | false | Нет |
dense | boolean | false | Нет |
bordered | boolean | false | Нет |
maxHeight | string | number | — | Нет |
tableMinWidth | string | number | — | Нет |
tableLayout | UiDataTableLayout | 'auto' | Нет |
rowClass | UiDataTableRowClass<TRow> | — | Нет |
caption | string | — | Нет |
ariaLabel | string | — | Нет |
filterable | boolean | true | Нет |
filterDialogTeleport | boolean | true | Нет |
lazy | boolean | false | Нет |
hasMore | boolean | true | Нет |
loadingMore | boolean | false | Нет |
lazyThreshold | number | 96 | Нет |
labels | Partial<UiDataTableLabels> | — | Нет |
Настройка внешнего вида
.users-table {
--cui-data-table-radius: 16px;
--cui-data-table-background: #ffffff;
--cui-data-table-header-background: #f8fafc;
--cui-data-table-row-hover-background: #f1f5f9;
--cui-data-table-row-striped-background: #fafafa;
--cui-data-table-border-color: #e2e8f0;
--cui-data-table-sort-color: #7c3aed;
--cui-data-table-sticky-background: #ffffff;
--cui-data-table-sticky-shadow-color: rgb(15 23 42 / 22%);
--cui-data-table-cell-padding-block: 0.75rem;
--cui-data-table-cell-padding-inline: 1rem;
--cui-data-table-row-min-height: 3rem;
}Токены можно задать глобально, отдельно внутри light/dark темы или только на конкретной таблице.
Доступность
Компонент использует нативные table, thead, th, tbody и caption. Сортируемые заголовки являются кнопками и обновляют aria-sort. Диалог фильтров удерживает фокус, закрывается по Escape и возвращает фокус на кнопку. Состояние загрузки передаётся через aria-busy, а горизонтальная прокрутка остаётся доступной с клавиатуры и touch-устройств.