@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.
Public API
Section titled “Public API”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.
Component rules
Section titled “Component rules”- 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
uinamespace 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
confirmationNamemakes people type the name before a rare, destructive action. Pending confirmation disables cancellation and Escape. - Biome rejects native
<form>, React Aria’sFormandformMethod/formActionoverrides 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.
Tests and generated styles
Section titled “Tests and generated styles”pnpm --filter @guestcraft/ui testpnpm --filter @guestcraft/ui typecheckpnpm --filter @guestcraft/ui theme:buildpnpm --filter @guestcraft/scripts test:integration test/integration/readme-examples.integration.test.tsVitest/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.