Table

A styled HTML table in parts: header, body, footer, row, heading cell, cell and caption.

Install

If this is the first component in the project, set up once. This writes ermine.json and copies the theme stylesheet. The installation guide covers Vue, Nuxt and Astro.

npx ermineui init
pnpm dlx ermineui init
yarn dlx ermineui init
bunx ermineui init

Then add the component:

npx ermineui add table
pnpm dlx ermineui add table
yarn dlx ermineui add table
bunx ermineui add table

That copies 13 files into src/components/ui/table/. Files you already have are kept.

Use

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
<script setup lang="ts">
import { Table } from '@/components/ui/table'
</script>

<template>
  <Table />
</template>

In Nuxt, with the auto-import setting, the tags work in templates without the import. In Astro, use it inside a Vue island. The props are documented in the source below, on each one.

What gets copied

FileWhere it goes
Table.vuesrc/components/ui/table/Table.vue
TableBody tbody sb-table-body.vuesrc/components/ui/table/TableBody tbody sb-table-body.vue
TableBody.vuesrc/components/ui/table/TableBody.vue
TableCaption caption sb-table-caption.vuesrc/components/ui/table/TableCaption caption sb-table-caption.vue
TableCaption.vuesrc/components/ui/table/TableCaption.vue
TableCell.vuesrc/components/ui/table/TableCell.vue
TableFooter tfoot sb-table-footer.vuesrc/components/ui/table/TableFooter tfoot sb-table-footer.vue
TableFooter.vuesrc/components/ui/table/TableFooter.vue
TableHead.vuesrc/components/ui/table/TableHead.vue
TableHeader thead sb-table-header.vuesrc/components/ui/table/TableHeader thead sb-table-header.vue
TableHeader.vuesrc/components/ui/table/TableHeader.vue
TableRow.vuesrc/components/ui/table/TableRow.vue
index.tssrc/components/ui/table/index.ts

The folders are the suggested ones. ermine.json says where yours are.

Source

This is exactly what the command copies. To install by hand, create each file below and the helpers listed above.

Table.vue 89 lines
<script setup lang="ts">
defineOptions({ inheritAttrs: false })
</script>

<template>
  <!-- The wrapper scrolls sideways, so a wide table never pushes the page wider on a phone. -->
  <div class="er-table-wrap">
    <table v-bind="$attrs" class="er-table"><slot /></table>
  </div>
</template>

<style>
@layer components {
  .er-table-wrap {
    /* Positioned, so anything visually hidden inside scrolls with the table instead of widening the page. */
    position: relative;
    width: 100%;
    overflow-x: auto;
  }

  .er-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
    caption-side: bottom;
  }

  .er-table-caption {
    margin-top: 0.75rem;
    color: var(--er-muted-foreground);
    font-size: 0.8125rem;
  }

  .er-table-row {
    border-bottom: 1px solid var(--er-border);
    transition: background-color var(--er-duration);
  }

  .er-table-body .er-table-row:last-child {
    border-bottom: 0;
  }

  .er-table-body .er-table-row:hover {
    background: color-mix(in oklab, var(--er-muted) 50%, transparent);
  }

  .er-table-row[data-state='selected'],
  .er-table-body .er-table-row[data-state='selected']:hover {
    background: var(--er-muted);
  }

  .er-table-head,
  .er-table-cell {
    padding: 0.625rem 0.75rem;
    text-align: left;
    vertical-align: middle;
  }

  .er-table-head {
    height: 2.5rem;
    padding-block: 0;
    color: var(--er-muted-foreground);
    font-weight: var(--er-weight-medium);
    white-space: nowrap;
  }

  .er-table-head[data-align='right'],
  .er-table-cell[data-align='right'] {
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  .er-table-head[data-align='center'],
  .er-table-cell[data-align='center'] {
    text-align: center;
  }

  .er-table-footer {
    border-top: 1px solid var(--er-border);
    background: color-mix(in oklab, var(--er-muted) 50%, transparent);
    font-weight: var(--er-weight-medium);
  }

  .er-table-footer .er-table-row {
    border-bottom: 0;
  }
}
</style>
TableBody tbody sb-table-body.vue 4 lines
<template>
  < class=""><slot /></>
</template>
TableBody.vue 4 lines
<template>
  <tbody class="er-table-body"><slot /></tbody>
</template>
TableCaption caption sb-table-caption.vue 4 lines
<template>
  < class=""><slot /></>
</template>
TableCaption.vue 4 lines
<template>
  <caption class="er-table-caption"><slot /></caption>
</template>
TableCell.vue 14 lines
<script setup lang="ts">
withDefaults(
  defineProps<{
    /** Use "right" for numbers, so the digits line up. */
    align?: 'left' | 'center' | 'right'
  }>(),
  { align: 'left' },
)
</script>

<template>
  <td class="er-table-cell" :data-align="align"><slot /></td>
</template>
TableFooter tfoot sb-table-footer.vue 4 lines
<template>
  < class=""><slot /></>
</template>
TableFooter.vue 4 lines
<template>
  <tfoot class="er-table-footer"><slot /></tfoot>
</template>
TableHead.vue 16 lines
<script setup lang="ts">
withDefaults(
  defineProps<{
    /** Use "right" for columns of numbers, so the digits line up. */
    align?: 'left' | 'center' | 'right'
    /** "col" for a column heading (the default), "row" for a heading at the start of a row. */
    scope?: 'col' | 'row'
  }>(),
  { align: 'left', scope: 'col' },
)
</script>

<template>
  <th class="er-table-head" :scope="scope" :data-align="align"><slot /></th>
</template>
TableHeader thead sb-table-header.vue 4 lines
<template>
  < class=""><slot /></>
</template>
TableHeader.vue 4 lines
<template>
  <thead class="er-table-header"><slot /></thead>
</template>
TableRow.vue 11 lines
<script setup lang="ts">
defineProps<{
  /** Highlight the row, e.g. while its checkbox is ticked. */
  selected?: boolean
}>()
</script>

<template>
  <tr class="er-table-row" :data-state="selected ? 'selected' : undefined" :aria-selected="selected || undefined"><slot /></tr>
</template>
index.ts 10 lines
/** Table — a styled HTML table in parts: header, body, footer, row, heading cell, cell and caption. */
export { default as Table } from './Table.vue'
export { default as TableBody } from './TableBody.vue'
export { default as TableCaption } from './TableCaption.vue'
export { default as TableCell } from './TableCell.vue'
export { default as TableFooter } from './TableFooter.vue'
export { default as TableHead } from './TableHead.vue'
export { default as TableHeader } from './TableHeader.vue'
export { default as TableRow } from './TableRow.vue'