# Table

Semantic tables with headers, footers, captions, scrolling, and an inset variant.

- Package: `@mielui/svelte`
- Component version: `1.0.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: components/_internal/inset-layout, utils.cn

## Install

~~~~sh
pnpm dlx @mielui/svelte add table
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Body

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Caption

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Cell

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Footer

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Head

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Header

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Root

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| variant | "default" \| "inset" \| undefined | 'default' | No | No | `inset` puts the header and footer on an outer frame and the body rows on a recessed surface. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Row

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### ScrollArea

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type {
    HTMLAttributes,
    HTMLTableAttributes,
    HTMLTdAttributes,
    HTMLThAttributes
} from 'svelte/elements';
import Root from './table.svelte';
import Body from './table-body.svelte';
import Caption from './table-caption.svelte';
import Cell from './table-cell.svelte';
import Footer from './table-footer.svelte';
import Head from './table-head.svelte';
import Header from './table-header.svelte';
import Row from './table-row.svelte';
import ScrollArea from './table-scroll-area.svelte';

export type TableProps = HTMLTableAttributes & {
    /** `inset` puts the header and footer on an outer frame and the body rows on a recessed surface. */
    variant?: 'default' | 'inset';
};
export type TableBodyProps = HTMLAttributes<HTMLTableSectionElement>;
export type TableCaptionProps = HTMLAttributes<HTMLElement>;
export type TableCellProps = HTMLTdAttributes;
export type TableFooterProps = HTMLAttributes<HTMLTableSectionElement>;
export type TableHeadProps = HTMLThAttributes;
export type TableHeaderProps = HTMLAttributes<HTMLTableSectionElement>;
export type TableRowProps = HTMLAttributes<HTMLTableRowElement>;
export type TableScrollAreaProps = HTMLAttributes<HTMLDivElement>;

export { Body, Caption, Cell, Footer, Head, Header, Root, Row, ScrollArea };
~~~~

## Examples

### Inset

~~~~svelte
<script lang="ts">
    import { Badge } from '@mielui/svelte/components/badge';
    import * as Table from '@mielui/svelte/components/table';

    const invoices = [
        {
            id: 'INV-001',
            status: 'Paid',
            method: 'Card',
            amount: '$250.00'
        },
        {
            id: 'INV-002',
            status: 'Pending',
            method: 'Bank transfer',
            amount: '$150.00'
        },
        {
            id: 'INV-003',
            status: 'Paid',
            method: 'Card',
            amount: '$350.00'
        }
    ];
</script>

<Table.ScrollArea class="max-w-xl" tabindex={0} aria-label="Invoices">
    <Table.Root variant="inset" class="min-w-[28rem]">
        <Table.Caption>Invoices for September.</Table.Caption>
        <Table.Header>
            <Table.Row>
                <Table.Head>Invoice</Table.Head>
                <Table.Head>Status</Table.Head>
                <Table.Head>Method</Table.Head>
                <Table.Head class="text-end">Amount</Table.Head>
            </Table.Row>
        </Table.Header>
        <Table.Body>
            {#each invoices as invoice (invoice.id)}
                <Table.Row>
                    <Table.Cell class="font-medium">{invoice.id}</Table.Cell>
                    <Table.Cell>
                        <Badge variant={invoice.status === 'Paid' ? 'success' : 'warning'}>
                            {invoice.status}
                        </Badge>
                    </Table.Cell>
                    <Table.Cell>{invoice.method}</Table.Cell>
                    <Table.Cell class="text-end">{invoice.amount}</Table.Cell>
                </Table.Row>
            {/each}
        </Table.Body>
        <Table.Footer>
            <Table.Row>
                <Table.Cell colspan={3}>Total</Table.Cell>
                <Table.Cell class="text-end">$750.00</Table.Cell>
            </Table.Row>
        </Table.Footer>
    </Table.Root>
</Table.ScrollArea>
~~~~

### Interactive

~~~~svelte
<script lang="ts">
    import { ArrowUpDownIcon as SortIcon } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import { Checkbox } from '@mielui/svelte/components/checkbox';
    import * as Table from '@mielui/svelte/components/table';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    const projects = ['Website', 'Dashboard', 'Mobile app'];

    let ascending = $state(true);
    let selected = $state<string[]>([]);
    let empty = $state(false);
    const rows = $derived(
        empty
            ? []
            : [...projects].sort((left, right) =>
                  ascending ? left.localeCompare(right) : right.localeCompare(left)
              )
    );

    function selectAll(checked: boolean) {
        selected = checked ? rows : [];
    }

    function toggleSort() {
        ascending = !ascending;
    }

    function toggleEmpty() {
        empty = !empty;
        selected = [];
    }

    function select(name: string, checked: boolean) {
        selected = checked
            ? [...new Set([...selected, name])]
            : selected.filter((value) => value !== name);
    }
</script>

<div class="flex w-full max-w-xl flex-col gap-3">
    <Table.ScrollArea tabindex={0} aria-label="Projects">
        <Table.Root>
            <Table.Header>
                <Table.Row>
                    <Table.Head class="w-12">
                        <Checkbox
                            aria-label="Select all projects"
                            checked={rows.length > 0 && rows.every((row) => selected.includes(row))}
                            onCheckedChange={selectAll}
                        />
                    </Table.Head>
                    <Table.Head aria-sort={ascending ? 'ascending' : 'descending'}>
                        <Button variant="ghost" size="sm" class="-ms-2" onclick={toggleSort}>
                            Project<HugeiconsIcon icon={SortIcon} size={14} />
                        </Button>
                    </Table.Head>
                </Table.Row>
            </Table.Header>
            <Table.Body>
                {#each rows as name (name)}
                    <Table.Row data-state={selected.includes(name) ? 'selected' : undefined}>
                        <Table.Cell>
                            <Checkbox
                                aria-label={`Select ${name}`}
                                checked={selected.includes(name)}
                                onCheckedChange={(checked) => select(name, checked)}
                            />
                        </Table.Cell>
                        <Table.Cell>{name}</Table.Cell>
                    </Table.Row>
                {:else}
                    <Table.Row>
                        <Table.Cell colspan={2} class="h-24 text-center text-foreground-muted">
                            No projects found.
                        </Table.Cell>
                    </Table.Row>
                {/each}
            </Table.Body>
        </Table.Root>
    </Table.ScrollArea>
    <div class="flex items-center justify-between gap-3">
        <p role="status" class="text-sm text-foreground-muted">{selected.length} selected</p>
        <Button variant="outline" size="sm" onclick={toggleEmpty}>
            {empty ? 'Restore rows' : 'Show empty state'}
        </Button>
    </div>
</div>
~~~~

### Usage

~~~~svelte
<script lang="ts">
    import * as Table from '@mielui/svelte/components/table';
</script>

<Table.ScrollArea tabindex={0} aria-label="Invoices">
    <Table.Root>
        <Table.Caption>September invoices</Table.Caption>
        <Table.Header>
            <Table.Row>
                <Table.Head>Invoice</Table.Head>
                <Table.Head>Amount</Table.Head>
            </Table.Row>
        </Table.Header>
        <Table.Body>
            <Table.Row>
                <Table.Cell>INV-001</Table.Cell>
                <Table.Cell>$250.00</Table.Cell>
            </Table.Row>
        </Table.Body>
    </Table.Root>
</Table.ScrollArea>
~~~~

For the rendered reference, visit [/docs/components/table](/docs/components/table).
