Skip to content

@guestcraft/tokens

The design tokens of GuestCraft (ADR 0018, analysis chapter 15): colours and their themes, spacing, radius, type, elevation, layers, motion, density and the size of targets, as typed data. Web and mobile share them; the components do not. It is MIT and imports nothing.

Components use semantic roles, never raw values: fgDanger means the colour of an error’s text in every theme, and only its value changes.

import { CONTRAST_PAIRS, DENSITY, PALETTES, checkContrast } from "@guestcraft/tokens";
PALETTES.dark.fgDanger; // "#FF7A7A"
DENSITY.compact.row; // 28: rows of the rates grid
checkContrast(brandPalette); // [] when a brand theme keeps every pair readable

The web app imports the same data as CSS custom properties, after Tailwind:

@import "@guestcraft/tokens/tokens.css";
Group Exports Rules
Colours PALETTES, COLOR_ROLES, THEMES, THEME_TRAITS Four themes: light and dark, the values the UI/UX research verified, and lightMore and darkMore for people who ask for more contrast. Dark surfaces lighten as they rise, with no pure black and no pure white text. Seven states of a stay or a unit, each with a fill, a text colour and a strip; colour is never the only signal, so the components add an icon, a pattern or words.
Contrast CONTRAST_PAIRS, contrastRatio, checkContrast, checkTheme Every pair a theme uses: text and controls on every surface, text on filled buttons, each state on its fill and on the page. Standard themes meet WCAG 2.2 AA (4.5:1 for text, 3:1 for controls, focus and strips); themes with more contrast reach 7:1 for text and 4.5:1 for the rest.
Scales SPACING, RADIUS, TYPE_SCALE, FONT_FAMILIES, FONT_WEIGHTS, ELEVATION, LAYERS, MOTION, EASING Spacing on a 4 px base. Inter 14/20 for the dashboard, 16/24 for guest pages and mobile, never below 12/16. Motion of 0 ms for data, 100 ms for hover, 150 ms for menus, 250 ms for dialogs; short fades only with reduced motion.
Density DENSITIES, DENSITY, TARGET Compact (rows of 28 px), standard (36 px, the default) and comfortable (44 px). Density changes spacing and heights, never the size of text. Targets are at least 24 × 24 px for a pointer and 48 × 48 px on touch screens.
CSS tokensCss, colorVariable, kebab, rem The stylesheet of css/tokens.css: names start with --gc-, lengths are in rem.
DTCG toDtcg The tokens of one theme and one density in the Design Tokens Format Module 2025.10, for design tools and token translators.

The providers of @guestcraft/ui set three attributes on an element, and tokens.css does the rest:

  • data-theme: light, dark or system;
  • data-contrast: standard, more or system, on the same element as data-theme;
  • data-density: compact, standard or comfortable.

system follows prefers-color-scheme and prefers-contrast. The server renders the attributes from the person’s saved choice, so no script runs before the page is painted and the page never flashes in the wrong theme. Touch screens (pointer: coarse) get 48 px targets, and prefers-reduced-motion shortens every movement to a fade.

  1. Change the data in src/.
  2. Run pnpm --filter @guestcraft/tokens css:build, which writes css/tokens.css, and pnpm --filter @guestcraft/ui theme:build.
  3. Run pnpm test. The tests:
    • compute the contrast of every pair of every theme with a formula of their own, written apart from the package’s;
    • fail when css/tokens.css differs from the data;
    • check that every role has a value in every theme, and that the CSS defines each one in every block of a theme.

A new colour that a component draws on a background joins CONTRAST_PAIRS first; the style test of @guestcraft/ui fails for a pair that is not there.

The fonts are Inter and JetBrains Mono, under the SIL Open Font License. The app serves the files; this package only names them.

scrim is a decorative black role in every theme. Modal overlays use it at 40% opacity so dark mode dims instead of lightening the page. No text or control is placed on this role; its compositing regression is separate from contrast pairs.