Klarhimmel UI

Data table

App, data table preview

Install

npx shadcn add @klarhimmel/app-data-table

Usage

import { Theme } from '@/components/klarhimmel/theme/style';
import { BlockSurface } from '@/components/klarhimmel/blocks/surface';
import AppDataTable from '@/components/klarhimmel/blocks/app-data-table';

export default function Section() {
  return (
    <Theme theme="brand">
      <BlockSurface>
        <AppDataTable props={{
          "eyebrow": "Bookings",
          "title": "Upcoming arrivals",
          "description": "Every booking at Exempelhotellet in the next two weeks. Click a column to sort.",
          "caption": "Upcoming arrivals, sortable by column",
          "columns": [
            {
              "label": "Guest",
              "align": "left",
              "sortable": true,
              "kind": "text"
            },
            {
              "label": "Room",
              "align": "left",
              "sortable": true,
              "kind": "text"
            },
            {
              "label": "Arrival",
              "align": "left",
              "sortable": true,
              "kind": "date"
            },
            {
              "label": "Nights",
              "align": "right",
              "sortable": true,
              "kind": "number"
            },
            {
              "label": "Total",
              "align": "right",
              "sortable": true,
              "kind": "number"
            }
          ],
          "rows": [
            {
              "cells": [
                "Maja Lindqvist",
                "Seaside suite",
                "12 Jun",
                "3",
                "4 800 kr"
              ],
              "keys": [
                "",
                "",
                "2026-06-12",
                "3",
                "4800"
              ]
            },
            {
              "cells": [
                "Nils Berg",
                "Garden room",
                "13 Jun",
                "2",
                "2 100 kr"
              ],
              "keys": [
                "",
                "",
                "2026-06-13",
                "2",
                "2100"
              ]
            },
            {
              "cells": [
                "Ada Ek",
                "Attic double",
                "13 Jun",
                "5",
                "5 250 kr"
              ],
              "keys": [
                "",
                "",
                "2026-06-13",
                "5",
                "5250"
              ]
            },
            {
              "cells": [
                "Oskar Holm",
                "Garden room",
                "15 Jun",
                "1",
                "1 050 kr"
              ],
              "keys": [
                "",
                "",
                "2026-06-15",
                "1",
                "1050"
              ]
            },
            {
              "cells": [
                "Sofia Nord",
                "Seaside suite",
                "16 Jun",
                "4",
                "6 400 kr"
              ],
              "keys": [
                "",
                "",
                "2026-06-16",
                "4",
                "6400"
              ]
            }
          ],
          "locale": "sv",
          "density": "comfortable",
          "selectable": false,
          "select": {
            "row": "Select",
            "all": "Select all rows",
            "selected": "selected",
            "clear": "Clear selection",
            "barLabel": "Actions for the selected rows",
            "actions": [
              "Export",
              "Archive"
            ]
          },
          "scrollHint": "Scroll sideways for more columns",
          "sort": {
            "active": "Arrival",
            "direction": "ascending"
          },
          "state": "filled",
          "messages": {
            "loading": "Loading bookings",
            "emptyTitle": "No arrivals in the next two weeks",
            "emptyBody": "New bookings show up here as soon as a guest reserves a room.",
            "emptyAction": "Add a booking",
            "errorTitle": "We could not load the bookings",
            "errorBody": "Check your connection and try again.",
            "retry": "Try again"
          }
        }} />
      </BlockSurface>
    </Theme>
  );
}

States

data table in 6 forms: filled, empty, loading, error, compact rows, row selection.

  • state: filled

    Bookings

    Upcoming arrivals

    Every booking at Exempelhotellet in the next two weeks. Click a column to sort.

    Upcoming arrivals, sortable by column
    Maja LindqvistSeaside suite12 Jun34 800 kr
    Nils BergGarden room13 Jun22 100 kr
    Ada EkAttic double13 Jun55 250 kr
    Oskar HolmGarden room15 Jun11 050 kr
    Sofia NordSeaside suite16 Jun46 400 kr
  • state: empty

    Bookings

    Upcoming arrivals

    Every booking at Exempelhotellet in the next two weeks. Click a column to sort.

    Upcoming arrivals, sortable by column

    No arrivals in the next two weeks

    New bookings show up here as soon as a guest reserves a room.

  • state: loading

    Bookings

    Upcoming arrivals

    Every booking at Exempelhotellet in the next two weeks. Click a column to sort.

    Upcoming arrivals, sortable by column

    Loading bookings

  • state: error

    Bookings

    Upcoming arrivals

    Every booking at Exempelhotellet in the next two weeks. Click a column to sort.

    Upcoming arrivals, sortable by column
  • compact rows

    Bookings

    Upcoming arrivals

    Every booking at Exempelhotellet in the next two weeks. Click a column to sort.

    Upcoming arrivals, sortable by column
    Maja LindqvistSeaside suite12 Jun34 800 kr
    Nils BergGarden room13 Jun22 100 kr
    Ada EkAttic double13 Jun55 250 kr
    Oskar HolmGarden room15 Jun11 050 kr
    Sofia NordSeaside suite16 Jun46 400 kr
  • row selection

    Bookings

    Upcoming arrivals

    Every booking at Exempelhotellet in the next two weeks. Click a column to sort.

    selected

    Upcoming arrivals, sortable by column
    Maja LindqvistSeaside suite12 Jun34 800 kr
    Nils BergGarden room13 Jun22 100 kr
    Ada EkAttic double13 Jun55 250 kr
    Oskar HolmGarden room15 Jun11 050 kr
    Sofia NordSeaside suite16 Jun46 400 kr

Notes

When to use it

A sortable table of records with optional row selection and bulk actions. For a handful of invoices use `app-billing-invoices`; for a table where nothing is sorted a simple comparison block is lighter.

Writing for it

- Each column needs a `kind` of text, number or date, and sorting follows it, so 800 kr comes before 12 000 kr. Set `locale` (10), such as sv, for the alphabet. - Add `keys` to a row (30 characters each) when the shown text cannot sort, for example an ISO date behind a text date. They win over the displayed cell. - The first cell in every row is its row header, so put the name there. Rows allow 2 to 6 cells and up to 10 rows.

Watch out for

Turning on `selectable` adds a checkbox column and a bulk action bar of at most 3 actions that appears only once something is picked. The selection exists only in the block, and selecting works only in the filled state.

Usage

The block in a page, with its default content (long lists shortened). Every prop is in the table below.

section.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { BlockSurface } from '@/components/klarhimmel/blocks/surface';
import AppDataTable from '@/components/klarhimmel/blocks/app-data-table';

export default function Section() {
  return (
    <Theme theme="brand">
      <BlockSurface>
        <AppDataTable props={{
          "eyebrow": "Bookings",
          "title": "Upcoming arrivals",
          "description": "Every booking at Exempe…",
          "caption": "Upcoming arrivals, sort…",
          "columns": [
            {
              "label": "Guest",
              "align": "left",
              "sortable": true,
              "kind": "text"
            }
          ],
          "rows": [
            {
              "cells": [
                "Maja Lindqvist"
              ],
              "keys": [
                ""
              ]
            }
          ],
          "locale": "sv",
          "density": "comfortable",
          "selectable": false,
          "select": {
            "row": "Select",
            "all": "Select all rows",
            "selected": "selected",
            "clear": "Clear selection",
            "barLabel": "Actions for the selecte…",
            "actions": [
              "Export"
            ]
          },
          "scrollHint": "Scroll sideways for mor…",
          "sort": {
            "active": "Arrival",
            "direction": "ascending"
          },
          "state": "filled",
          "messages": {
            "loading": "Loading bookings",
            "emptyTitle": "No arrivals in the next…",
            "emptyBody": "New bookings show up he…",
            "emptyAction": "Add a booking",
            "errorTitle": "We could not load the b…",
            "errorBody": "Check your connection a…",
            "retry": "Try again"
          }
        }} />
      </BlockSurface>
    </Theme>
  );
}

Props

The type, default and text limit of every prop. On a real page, over-limit text is kept and checkSections flags it (Installation, Check a page before you ship); this live preview shows the default until it fits. The panel above edits all props.

PropTypeDefaultDescription
eyebrowstring'Bookings'Text, up to 60 characters. Optional; empty is not rendered.
titlestring'Upcoming arrivals'Text, up to 120 characters.
descriptionstring'Every booking at Exempelhotellet in…'Text, up to 300 characters. Optional; empty is not rendered.
captionstring'Upcoming arrivals, sortable by colu…'Text, up to 80 characters.
columns{ label, align, sortable, kind }[]5 itemsList of 2 to 6. Each item: label, align ('left' | 'right'), sortable, kind ('text' | 'number' | 'date').
rows{ cells, keys }[]5 itemsList of 1 to 10. Each item: cells, keys (optional).
localestring'sv'Text, up to 10 characters.
density'comfortable' | 'compact''comfortable'One of 'comfortable', 'compact'.
selectablebooleanfalseSee the type.
select{ row, all, selected, clear, barLabel, actions }{…}Fields: row, all, selected, clear, barLabel, actions.
scrollHintstring'Scroll sideways for more columns'Text, up to 40 characters.
sort{ active, direction }{…}Fields: active, direction ('ascending' | 'descending').
state'filled' | 'empty' | 'loading' | 'error''filled'One of 'filled', 'empty', 'loading', 'error'.
messages{ loading, emptyTitle, emptyBody, emptyAction, errorTitle, errorBody, retry }{…}Fields: loading, emptyTitle, emptyBody, emptyAction, errorTitle, errorBody, retry.
headingLevel'1' | '2'-One of '1', '2'. Optional; empty is not rendered.

Install

Installs

app-data-table
Uses
  • Banner
  • Button
  • EmptyState
  • Skeleton
About
A data table: sort buttons that order by what a column holds (text, numbers, dates; aria-sort), a first column that stays in view while the rest scrolls with an edge fade, comfortable or compact rows, optional row selection with select…
States
Filled, empty, loading and error. Headers sort in place and carry aria-sort; the region scrolls sideways on a phone.