Skip to content

@guestcraft/ui

GuestCraft’s private, AGPL-3.0-only web design system: React Aria components, Tailwind 4 styles and the semantic tokens of @guestcraft/tokens. React, React DOM, Tailwind and use-intl are peers. Providers make language, property time zone, theme, contrast and density explicit. The kit serves front-desk, housekeeping and revenue screens; application code owns persistence, permissions and workflow state.

Load the English catalog on the server and pass it to UIProvider. This example accepts the property’s IANA time zone and wraps any application content without undeclared globals:

import { formattingSample, SOURCE_LANGUAGE } from "@guestcraft/i18n";
import { loadMessages } from "@guestcraft/i18n/catalogs";
import { UIProvider } from "@guestcraft/ui";
import type { ReactNode } from "react";
export async function Example({
children,
timeZone,
}: {
readonly children: ReactNode;
readonly timeZone: string;
}) {
const locale = "en-GB";
const messages = await loadMessages(SOURCE_LANGUAGE);
return (
<UIProvider
locale={locale}
serverSample={formattingSample(locale)}
messages={messages}
timeZone={timeZone}
theme="system"
density="standard"
>
{children}
</UIProvider>
);
}

In the application, choose locale with englishFormattingLocale from the request’s preferences. Pass a serverSample at the root only: the browser first hydrates using the server’s locale, then switches to world English (en-001) if its ICU data cannot reproduce the sample. Catalog text remains English. Dialogs, popovers and toasts render within the provider’s portal container and inherit its settings.

The app’s stylesheet imports these in order; make the @source path relative to that file’s location and the installed package:

@import "tailwindcss";
@import "@guestcraft/tokens/tokens.css";
@import "@guestcraft/ui/theme.css";
@source "../node_modules/@guestcraft/ui/src";

For Next.js, put documentAttributes({ locale, theme, contrast, density }) on the server layout’s <html> to set document preferences before paint, including viewport scrollbars. Keep UIProvider inside it. Components/providers have client boundaries; documentAttributes is server-safe. Add @guestcraft/ui, @guestcraft/tokens and @guestcraft/i18n to transpilePackages and point Tailwind at the UI sources.

The root entry point exports the components, hooks and their public types. @guestcraft/ui/theme.css is the generated semantic Tailwind theme.

Group Exports and contracts
Providers UIProvider, LocaleProvider, ThemeProvider, DensityProvider; useLocaleSettings, useTheme, useDensity; documentAttributes. Locale/time zone are explicit; theme, contrast and density can nest.
Actions Button, IconButton. Variants and sizes share density-based targets; pending buttons retain focus and reject repeated activation. Icon buttons require a label and icon.
Fields Field, Input, Textarea, TextField, Select, ComboBox, Checkbox, Switch, RadioGroup, NumberField, SearchField. Use visible labels, necessity, descriptions and linked errors. ComboBox and RadioGroup accept { id, label } options. Textarea and ComboBox expose input refs for focus recovery.
Money MoneyInput, formatStaffMoney. Input/output use exact decimal strings with the currency’s allowed precision. Staff displays use the locale’s number separators and currency code. Keep the reported validation problem with the value and block submission while invalid. NumberField is for counts.
Dates DatePicker, DateRangePicker, Calendar, RangeCalendar. Values use @internationalized/date; convert explicitly to the domain’s Temporal dates at feature boundaries.
Dialogs and overlays Dialog, ConfirmDialog, Sheet, Popover, Tooltip, Menu. Managed focus, controlled state and labeled actions; asynchronous confirmation stays pending and admits one action per opening. The parent closes it and handles failure.
Navigation and lists Tabs, Breadcrumbs, Table, DataTable<T>. DataTable takes columns, rows, row IDs and a label, with optional row actions. Loading, empty, error and retry states are explicit. Sorting applies to the supplied server page; the caller owns cursor pagination.
Feedback ToastProvider, useToast, UNDO_TIMEOUT, Banner, ErrorSummary. Errors use field feedback/banners, rather than toasts. ErrorSummary links fields for focus recovery. Undo lasts ten seconds; F6 reaches the toast region. An optional labeled action closes the notification when activated.
Presentation Avatar, Badge, Card, EmptyState, Kbd, PageHeader, Separator, Skeleton, StatTile, StatusBadge. Avatar uses initials without remote images. PageHeader supplies the page h1. StatusBadge pairs typed status with catalog text/icon; compact housekeeping badges keep accessible full names.
Planning PlanningGrid and Planning* types. Controlled window, units/groups, availability, stays and optional OOO/OOS blocks; callbacks for opening records, reservation selection, moves and window changes, with read-only/loading/error states.

Planning dates are Temporal.PlainDate and its window is 7, 14, 31 or 60 nights. Unit rows show labeled block bars with keyboard opening. Move and selection callbacks request an operation; the parent must check permissions, validate and persist it. The grid does not save reservations itself. See planning, room blocks and stay moves/splits.

This package exposes no HTTP endpoints. Screens use the SDK/React clients and the generated API reference for endpoint contracts; rendering a component grants no server permission.

  • Use semantic tokens and logical CSS properties, with a visible focus outline. State needs words/icons as well as color. Controls honor the density target and touch-target minimum.
  • Put user-visible strings in the shared English catalog, using the ui namespace for component messages. Locale-aware numbers, dates, currency and units stay explicit.
  • Preserve current input and connect validation errors to their fields. MoneyInput reports every edit so Enter submits the latest value; display validation according to the form’s interaction state.
  • Confirmations name both choices and start with the safe choice. Routine reversible operations can offer Undo; a confirmationName makes people type the name before a rare, destructive action. Pending confirmation disables cancellation and Escape.
  • Biome rejects native <form>, React Aria’s Form and formMethod/formAction overrides in production UI sources, so a form submitted before hydration always POSTs and field values never enter the URL. Test fixtures may use native forms. Form containers live in the web app (PostForm); move it into this package before adding a shared form container.

The kit adapts the Mira React Aria base of shadcn/ui; upstream MIT notices are retained in NOTICE.md. ADR 0018 and ADR 0023 record its design decisions.

Terminal window
pnpm --filter @guestcraft/ui test
pnpm --filter @guestcraft/ui typecheck
pnpm --filter @guestcraft/ui theme:build
pnpm --filter @guestcraft/scripts test:integration test/integration/readme-examples.integration.test.ts

Vitest/jsdom tests use Testing Library, axe-core and keyboard interaction/focus assertions. They cover component states, pseudo-locales (en-XA, ar-XB), real RTL rendering, and fail on React, React Aria or use-intl warnings. Style tests compile component classes with Tailwind, checking semantic colors, logical properties, focus rules and target-size CSS. A drift test compares css/theme.css with the token generator. The README’s TSX example is compiled with the package’s strict settings.

jsdom measures neither layout nor rendered contrast. Token contrast checks and generated CSS checks have that limit; production browser/layout verification lives in apps/web’s browser suites. Those checks are required for affected operator workflows and are separate from component tests.