Skip to content

Theme tokens and scales

View as Markdown

@sapporta/ui/index.css: the --sap-* palette, radius, and elevation variables, the sap-* Tailwind theme scales, and the shadcn-compatible aliases. cn and extendCn from @sapporta/ui/cn.

Every Sapporta primitive, the shell, the table, report, auth, and account screens, and the grid preset read these tokens. One declaration changes all of them.

The generated packages/frontend/src/app.css imports Tailwind, then Sapporta’s stylesheets, then holds the application’s own CSS, which has the last word:

@import "tailwindcss";
@import "@sapporta/ui/index.css";
@import "@sapporta/grid/index.css";
@import "@sapporta/frontend/index.css";
@source "./";
/* Palette, radii, and elevation: runtime variables. */
:root {
--sap-brand: #2c6a4f;
--sap-numeric-negative: var(--sap-fg);
--sap-radius: 8px;
}
[data-theme="dark"] {
--sap-brand: #7cc4a0;
}
/* Type, tracking, and height tiers: Tailwind theme scales. */
@theme inline {
--text-sap-body: 16px;
--height-sap-row: 48px;
--height-sap-ctl: 44px;
}
body {
font-size: 16px;
}
  • Palette, radius, and elevation tokens are CSS variables on :root and [data-theme="dark"]. Redeclare them under the same selectors.
  • Type, tracking, and height tiers are Tailwind theme variables. Redeclare them in an @theme inline block.
  • The body base rule sets 13px directly. An application on a larger scale sets its own body font size.

useDocumentTheme() decides which palette applies; see Theme mode.

The light palette sits on :root and the dark palette on [data-theme="dark"], with identical names. Each --sap-* token is also a Tailwind colour, for bg-*, text-*, border-*, and ring-*.

TokenUtility namePaints
--sap-bgsap-bgPage background
--sap-surfacesap-surfacePage body, cards, grids, popovers
--sap-sidebarsap-sidebarStatus bar, compact bottom navigation, grid header fallback
--sap-nested-bgsap-nestedNested grid levels, muted wash
--sap-chip-bgsap-chipChips, secondary buttons
--sap-row, --sap-row-hoversap-row, sap-row-hoverRows, and the hover wash behind rows, menu items, and bg-accent
--sap-border, --sap-border-strong, --sap-border-softsap-border, sap-border-strong, sap-border-softBorders, from default to strongest to faintest
--sap-fg, --sap-fg-soft, --sap-fg-muted, --sap-fg-subtlesap-fg, sap-soft, sap-muted, sap-subtleText, from primary to faintest
--sap-brand, --sap-brand-softsap-brand, sap-brand-softBrand emphasis and its tint
--sap-selectionsap-selectionActive and selected grid cells
--sap-focus-ringsap-focus-ringFocus rings
--sap-link, --sap-drill-down-link, --sap-drill-down-link-softsap-link, sap-drill-down-link, sap-drill-down-link-softLinks and report drill-through links
--sap-loading-indicator-barsap-loading-indicator-barLoading bar
--sap-positive, --sap-warningsap-positive, sap-warningPositive figures and warnings
--sap-negativesap-negativeErrors, error text, and destructive actions
--sap-numeric-negativesap-numeric-negativeNegative figures; follows --sap-negative unless set
--sap-active-nav-bg, --sap-nav-count-bgsap-active-nav, sap-nav-countAvatar tiles, the compact navigation’s active item, nav counts
--sap-nav-bg, --sap-nav-fg, --sap-nav-iconsap-nav, sap-nav-fg, sap-nav-iconNavigation sidebar plane, row text, and icons
--sap-nav-section, --sap-nav-hover-bg, --sap-nav-selected-bgsap-nav-section, sap-nav-hover, sap-nav-selectedSidebar section labels, row hover, and the current page’s wash
--sap-kbd-bg, --sap-kbd-inverted-bgsap-kbd, sap-kbd-invertedKeyboard hint chips, on light and on filled surfaces

--sap-numeric-negative paints colorRule: "negative", negative signed values, and negative report summary figures. Set it apart from --sap-negative when negative numbers are ordinary data, such as money out in a ledger, so that errors stay red and figures stay in ink.

The --sap-nav-* tokens paint the navigation sidebar. Redeclare them to restyle it without moving --sap-sidebar or --sap-active-nav-bg, which also paint the status bar, grid headers, the compact bottom navigation, and avatar tiles.

TokenDefaultUtilityUsed by
--sap-radius-sm3pxrounded-smContext menu items, close and chevron buttons
--sap-radius6pxrounded-mdButtons, inputs, badges, account menu items
--sap-radius-lg8pxrounded-lgMenus, tooltips, grid popups, the account menu
--sap-radius-xl10pxrounded-xlPopovers, dialogs, alert dialogs

--radius, --radius-sm, --radius-lg, and --radius-xl are aliases for shadcn components and read the --sap-radius* values. Override the --sap-radius* names.

--sap-shadow-elevated, emitted as shadow-sap-elevated, is the one shadow for floating layers: popovers, menus, dialogs, sheets, tooltips, a revealed collapsed sidebar, and the grid’s header menu and editor popups. Surfaces in the page flow, such as buttons, inputs, badges, checkboxes, and switches, carry no shadow.

The unprefixed names keep shadcn utilities such as bg-primary, text-muted-foreground, and bg-accent working. Each delegates to a --sap-* token, so override the canonical token rather than the alias.

AliasReads
--background, --foreground--sap-bg, --sap-fg
--card, --popover--sap-surface
--primary, --primary-foreground--sap-fg, --sap-bg
--secondary--sap-chip-bg
--muted, --muted-foreground--sap-nested-bg, --sap-fg-muted
--accent--sap-row-hover
--destructive, --destructive-foreground--sap-negative, --sap-bg
--border, --input--sap-border
--ring--sap-brand
--sidebar-accent--sap-active-nav-bg

A base rule in @layer base sets border-color: var(--border) on every element, so a border utility without a colour draws in --sap-border. An application’s own base rules still take precedence.

Ten tiers, named by role. Tailwind’s own text-xs to text-lg remain available.

UtilityDefaultUsed for
text-sap-tiny9pxSort indicators and other tertiary glyphs
text-sap-micro10pxStat card labels
text-sap-label10.5pxSection labels, grid column headers, navigation counts
text-sap-meta11pxHelper text, badges, tooltips, status bar, card-view level titles
text-sap-menu11.5pxMenu items, select cells, page header subtitle
text-sap-data12pxInline data: IDs, dates, references, chips
text-sap-emph12.5pxButtons, inputs, search
text-sap-body13pxData rows, page header title, labels, toasts
text-sap-mark14.5pxWordmark
text-sap-display18pxDialog, sheet, and screen headings; stat values
UtilityDefaultUsed for
tracking-sap-display0Wordmark, stat values
tracking-sap-head0.06emGrid column headers, uppercase labels
tracking-sap-label0.06emStat card labels
tracking-sap-section0.08emMenu and view-switch section labels

The height tiers are registered under --height-*, so they produce h-sap-* and min-h-sap-* utilities and no padding, width, or gap utilities.

UtilityDefaultUsed for
h-sap-row32pxData rows
h-sap-header29pxGrid header, child rows, compact buttons
h-sap-ctl30pxButtons, inputs, search, pagers, navigation items, combobox, filters
h-sap-bar24pxStatus bar, chip strips
h-sap-topbar52pxPage header

The grid’s own spacing, such as cell padding and nested indents, is tuned through --sap-grid-* variables; see Grid styling.

cn(...inputs) joins class names and resolves Tailwind conflicts, the later class winning. It knows the text-sap-*, tracking-sap-*, and h-sap-* scales, so cn("text-sap-body", "text-sap-fg") keeps a size and a colour, and a caller’s h-11 replaces a component’s h-sap-ctl.

An application that adds its own scales through @theme registers their names once at startup, before anything renders:

import { extendCn } from "@sapporta/ui/cn";
extendCn({
text: ["display", "title", "body"],
spacing: ["control"],
});
  • text names font sizes: --text-<name>, used as text-<name>.
  • tracking names letter spacings: --tracking-<name>, used as tracking-<name>.
  • spacing names lengths in --height-<name> or --spacing-<name>, used as h-<name>, min-h-<name>, w-<name>, p-<name>, and so on.

The registration applies to every cn call, including those inside Sapporta’s components, so application classes passed through className merge correctly. An unregistered text-<name> reads as a colour, and cn drops whichever of two such classes comes first.