# Clean Table

A low-noise table for admin screens. Segmented tabs switch the data set, the search field filters every column, and rows alternate with a soft tint instead of borders. Dates and counts can use the monospace face. Click a row (or press Enter on it) to select it; the footer shows the selection, the total and the page. It is built on [TanStack Table](https://tanstack.com/table) v9. Press `/` to focus the search in the demo.

<demo-clean-table>



</demo-clean-table>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/clean-table.json"
```

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import CleanTable from '~/components/ui/clean-table/CleanTable.vue';
  import CleanTableStatus from '~/components/ui/clean-table/CleanTableStatus.vue';
  import type {
    CleanTableColumn,
    CleanTableTab,
  } from '~/components/ui/clean-table/types';

  interface Project {
    id: string;
    project: string;
    created: string;
    applicants: number;
    status: 'progress' | 'complete' | 'error';
  }

  const tabs: CleanTableTab[] = [
    { value: 'projects', label: 'Projects' },
    { value: 'applications', label: 'Applications' },
  ];

  const columns: CleanTableColumn<Project>[] = [
    { key: 'project', header: 'Project' },
    { key: 'created', header: 'Created', mono: true, muted: true },
    {
      key: 'applicants',
      header: 'No. of Applicants',
      mono: true,
      align: 'center',
    },
    { key: 'status', header: 'Status' },
  ];

  const tab = ref('projects');
  const projects: Project[] = [
    {
      id: '1',
      project: 'Tech Innovations Inc.',
      created: '04/12/2019',
      applicants: 567,
      status: 'progress',
    },
  ];
</script>

<template>
  <CleanTable
    v-model:tab="tab"
    :tabs="tabs"
    :columns="columns"
    :data="projects"
    row-key="id"
  >
    <template #cell-status="{ value }">
      <CleanTableStatus :status="value" />
    </template>
  </CleanTable>
</template>
```

The table does not filter by tab itself. Watch `tab` and pass the matching rows to `data`. Switching tabs resets the page, the search and the selection.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        columns
      </code>
    </td>
    
    <td>
      <code>
        CleanTableColumn[]
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Columns to show. See the column options below.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        data
      </code>
    </td>
    
    <td>
      <code>
        TRow[]
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Rows to show.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        row-key
      </code>
    </td>
    
    <td>
      <code>
        keyof TRow
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Field that uniquely identifies a row. Used for selection.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tab
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      <code>
        v-model
      </code>
      
      . The active tab value.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tabs
      </code>
    </td>
    
    <td>
      <code>
        CleanTableTab[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Segmented tabs above the table. Hidden when empty.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        page-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Rows per page.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        total
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Total shown in the footer, e.g. a server count. Defaults to the filtered row count.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        search-placeholder
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Search'
      </code>
    </td>
    
    <td>
      Placeholder for the search field.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shortcut
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'mod+k'
      </code>
    </td>
    
    <td>
      Key that focuses the search. <code>
        mod+k
      </code>
      
       is ⌘K or Ctrl+K. <code>
        ''
      </code>
      
       turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        empty-text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'No results.'
      </code>
    </td>
    
    <td>
      Shown when no row matches.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        caption
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Screen-reader caption for the table.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root element.
    </td>
  </tr>
</tbody>
</table>

## Column options

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        key
      </code>
    </td>
    
    <td>
      <code>
        keyof TRow
      </code>
    </td>
    
    <td>
      Row field for the column. Also names the <code>
        #cell-<key>
      </code>
      
       slot.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        header
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Header text.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        align
      </code>
    </td>
    
    <td>
      <code>
        'start' | 'center' | 'end'
      </code>
    </td>
    
    <td>
      Text alignment.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mono
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Uses the monospace face with tabular numbers.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        muted
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Uses the muted text color.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sortable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Set <code>
        false
      </code>
      
       to turn off sorting on the column.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Width class for the column, e.g. <code>
        w-40
      </code>
      
      . Long text truncates.
    </td>
  </tr>
</tbody>
</table>

## Slots and events

<table>
<thead>
  <tr>
    <th>
      Name
    </th>
    
    <th>
      Props
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        #cell-<key>
      </code>
      
       slot
    </td>
    
    <td>
      <code>
        { row, value }
      </code>
    </td>
    
    <td>
      Replaces the content of one column.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        row-click
      </code>
      
       event
    </td>
    
    <td>
      <code>
        row
      </code>
    </td>
    
    <td>
      Fires when a row is clicked.
    </td>
  </tr>
</tbody>
</table>

`CleanTableStatus` renders the status pill. Its `status` prop is `'progress'`, `'complete'` or `'error'`; pass `label` to change the text.

## Accessibility

- Sortable headers are buttons and set `aria-sort`.
- Rows are focusable. Enter or Space selects a row, and selected rows set `aria-selected`.
- The tabs use `role="tab"` with `aria-selected`.
- The footer count is announced through `aria-live`.
