@lifebound-labs/react
v0.2.0-beta.5
Published
Lifebound React components, portable themes and scoped overlays
Readme
Lifebound React
Maintained UI package, first external candidate 0.1.0-beta.0. The maintained primitive implementation is here; the showcase imports it through public exports; Learning remains a monorepo consumer. Requirements: prototype spec, delivery record.
Integration
A Tailwind v4 consumer owns exactly one bootstrap and imports the package's mappings:
@import "tailwindcss";
@import "@lifebound-labs/react/styles.css";The library stylesheet includes local Plus Jakarta Sans assets, generated variables, animation utilities, and package source discovery. It deliberately does not bootstrap Tailwind. A framework-independent consumer may import theme.css and fonts.css separately.
import { Button } from "@lifebound-labs/react/components/button"
import { ThemeProvider } from "@lifebound-labs/react/providers/theme-provider"
import { builtInLibrary, resolveTheme } from "@lifebound-labs/react/theme/core"
const themes = {
light: resolveTheme(builtInLibrary, "learning", "light"),
dark: resolveTheme(builtInLibrary, "learning", "dark"),
}
<ThemeProvider themes={themes} mode="system" libraryId={builtInLibrary.id}>
<Button>Start learning</Button>
</ThemeProvider>The provider is controlled. The application owns selection, preferences and persistence. system reads the current OS media query immediately on the client and subscribes to live changes through useSyncExternalStore; it does not overwrite either saved mode. Imports are SSR safe: theme APIs do not require React, window, storage or a DOM. Provider effects apply variables only after mounting. SSR integrations should render their initial mode/data attributes at the root to avoid an initial light-mode frame.
Document mode targets html; body portals inherit its variables. For an element-scoped preview, pass a stable mounted HTMLElement as target. The provider creates a portal container inside that target. Dialog, Select, DropdownMenu, Tooltip, Popover, Sheet, Drawer and Toast use it automatically; explicit Portal container props remain available. Avoid clipping/transforming the scoped ancestor of fixed overlays. Several independent full-page previews should use separate documents, as the showcase does.
Appearance contracts
Neutral zinc supporting surfaces are shared by neutral, Learning, Connect, Quest, Spark, ID, Survey, VTT and Project Cockpit presets. upstream-reference is labeled separately and uses the recorded system font and compact geometry. Default Lifebound typography is bundled Plus Jakarta Sans. Project Cockpit uses muted steel blue (preset ID cockpit): #48618A / white in light mode, #9DB4DC / #14171C in dark mode. Product primary colors preserve the existing palette material; neither gradients nor colored secondary buttons are default treatments.
Control, panel and overlay radii are variables; dimensions are bounded to 0–64px / 0–4rem. Comfortable controls are 48px, compact controls 32px; size variants adjust centrally. The default density auto is compact on a fine pointer and comfortable on a coarse one, switched by a CSS media query so the first paint is right and a pointer change needs no reload. Set density: "compact" or "comfortable" on a theme to override it; explicit compact keeps its 44px minimum touch targets on a coarse pointer. Button size="review" is the shared two-line study-action size. Density changes component padding and height, never Tailwind's global spacing scale. Switch track/thumb stay pills to preserve their mechanical meaning; Checkbox radius follows the control value with a 4px cap to retain its square selection shape; Tooltip's arrow retains its fixed geometry.
Consumers may position/space components and make them full width. They choose variants and sizes instead of changing internal padding, height, radius, fill or focus. CardContent/Header/Footer permit slot spacing and composition; CardTitle permits type scale/weight. Font family and semantic pairs belong to the theme. See shared lint contracts. Definition files and validated editor custom properties have narrow exceptions. Named app CSS still needs review: JSX lint cannot infer all stylesheet effects.
Component inventory and motion
All 26 catalog entries are maintained public components. The eleven additions are Accordion, Avatar, Calendar, Combobox, DataTable, Drawer, Popover, RadioGroup, Sheet, Sidebar and Toast. Existing audited Civic V2 Radix wrappers were adapted to Lifebound variables and portal ownership. Calendar uses pinned DayPicker 9.14.0, Combobox uses cmdk 1.1.1 with Popover, and Drawer uses Vaul 1.1.2. Drawer enables initial focus by default; callers may explicitly override autoFocus. Sidebar renders one child tree at a time and returns focus to its mobile trigger on dismissal.
DataTable has typed accessor/cell columns, required stable row IDs, immutable accent-insensitive filtering, stable numeric/date/string sorting, clamped local pagination and optional controlled row/page selection. Loading, empty and error rows are semantic states. It is a client-side component; server pagination, virtualization and editable cells require separate contracts. Supply selectedIds together with onSelectionChange to retain controlled selection.
Toast composes Radix Provider/Root/Title/Description/Action/Close/Viewport, including duration, hover/focus pause, keyboard entry, swipe and action altText. Closing the last focused toast restores the pre-notification focus while the viewport listeners are still alive; repeated notifications keep a working expiry clock. Applications own queues and business actions; the example uses local undo/retry feedback.
Switch transitions Tailwind v4 translate, Buttons animate their pressed scale, Accordion animates measured height, Calendar animates month changes, Sidebar animates width and overlays animate entry/exit. Native details animate height progressively when interpolate-size is supported; older engines keep working instant disclosures. Reduced-motion rules shorten animations/transitions and suppress Button press scaling, including details-content. Calendar’s dense seven-column date grid uses 32px cells and 40px navigation controls so it fits narrow inline examples; do not reduce these sizes in consumers.
Lifebound components
Composed components shared by Lifebound products live next to the primitives and use the same import paths (@lifebound-labs/react/components/wizard, review-summary, date-field, time-field, share-link, person, status-strip, date-badge, split-workspace). They own their internal spacing, type and surfaces; apps own page placement and content. The Wizard reacts to its container width, not the viewport, so it works in a split view or a showcase frame. A component belongs here when two or more Lifebound products use it or it is clearly product-neutral; Connect-only screens and wording stay in Connect.
Split workspace
SplitWorkspace lays out a SplitMain and up to two SplitPanels (side="start" or "end"), which sit on their side whatever the source order. Each panel has a window splitter on its inner edge: drag it, or focus it and use the arrow keys (16 px, 64 px with Shift), Home, End and, with collapsible, Enter.
<SplitWorkspace minMainSize={320}>
<SplitPanel side="start" handleLabel="Resize rooms" size={rooms} minSize={200} maxSize={420}
onSizeChange={setRooms} onSizeCommit={saveRooms}>{list}</SplitPanel>
<SplitMain>{chat}</SplitMain>
<SplitPanel side="end" handleLabel="Resize plan" defaultSize={340} collapsible
collapsed={planClosed} onCollapsedChange={setPlanClosed}>{plan}</SplitPanel>
</SplitWorkspace>- Sizes are px.
sizeis the requested width: a narrow container shrinks a panel down tominSize, and it returns tosizewhen there is room again. Dragging and keys stop where the main area would get narrower thanminMainSize. - Persistence belongs to the consumer:
onSizeChangefires while dragging,onSizeCommitonce a change is final, so store on commit and pass the value back assize. Cancelling a drag with Escape or a pointer cancel restores the previous size without a commit. - Collapsing keeps the content mounted and removes it from the tab order and from view, so drafts and scroll positions survive. The handle stays reachable; a consumer button can drive
collapsedtoo. - Narrow screens stay a consumer decision: below the sum of the minimums the main area gives way first and the workspace clips, so switch to your own list and detail navigation before that point.
handleLabelis the handle's accessible name; pass it in the interface language.
Contrast reports
The DOM-free theme API additionally exports measureContrast, createContrastReport, sampleBackground, contrastCSV and contrastMarkdown. Reports contain intended pairs, complete text/surface matrix and optional background samples. Foreground/background alpha is composed against a known opaque backdrop; unresolved translucency remains unrated. APCA uses signed experimental Lc and does not replace WCAG thresholds. Gradient sampling is an approximate square 11×11 sRGB grid, not a conformance guarantee for every layout or point.
Theme library and portability
@lifebound-labs/react/theme exports types, validateLibrary, parseDraft, resolveTheme, builtInLibrary, copy-on-write helpers, selectThemes, JSON/CSS codecs and collision-aware merging. Schema errors include field paths and block application/export. Low text/focus contrast and transparent colors produce separate review diagnostics and do not block experimental custom themes.
The strict v1 grammar accepts hex, numeric RGB/RGBA, HSL/HSLA and OKLCH literals, plus typed semantic token references. Arbitrary CSS, URLs, var/calc expressions and unknown/cyclic references are rejected. Fonts are jakarta and system. Backgrounds contain fallback color plus up to eight ordered solid/linear/radial layers with up to twelve ordered stops. First layer paints on top. Backgrounds remain separate from component surfaces.
Author src/themes/library.json, then run:
pnpm --filter @lifebound-labs/react themes:generateDo not hand-edit generated src/theme.css. A whole-library export preserves unused background definitions; a selected export includes the dependency closure once. Copy-on-write detaches a palette/background only when edited. Import collision policies are keep-both, replace-custom, and skip; built-ins are always protected.
Supported CSS dialect
Generated v1 CSS contains a URI-encoded, comment-safe library metadata record and a @layer lifebound-theme. Every theme has separate light :not(.dark) and dark .dark scopes with library/theme data attributes. Exactly the default theme has unselected root rules.
The CSS AST importer reads scopes and declarations. Supported edited declarations take precedence over metadata, including mode colors and typed backgrounds. Derived sidebar aliases and --radius follow edited canonical values unless independently changed inconsistently. Geometry, density, padding and fonts must agree across modes. Density heights must match a complete supported three-height set or the generated auto references, and the generated :root density slots and their (pointer: coarse) switch are the only accepted media rule; padding roles accept independent bounded literal lengths. Missing/duplicate scopes or declarations, !important, unsupported at-rules and unsafe expressions are rejected. Unknown declarations/selectors are reported. No imported rules are executed or injected.
Without metadata, only top-level :root, .dark and :root.dark semantic color literals and a literal --radius are recognized. Unsupported expressions/selectors/at-rules are reported; missing values receive neutral defaults. This is a bounded shadcn CSS adapter, not a general CSS/SCSS interpreter. See codec fixtures for exact equivalence guarantees.
Baseline and migration
Reference snapshot records existing audited Civic React V2 source at commit 8674563f03aaac2e0e7085bac8004927d60e8eb4, Radix/Nova configuration, shadcn 4.2.0 and Radix 1.4.3. It is the repository's upstream-derived baseline, including prior Civic changes; it is not claimed to be a fresh unmodified registry download. Original files were not overwritten. Radix behavior/markup stays; approved differences are neutral secondary/accent surfaces, shared geometry/density, solid destructive buttons, full-strength focus rings, loading/review sizes and controlled portal ownership. components.json pins the chosen style. The Base UI/Luma preset in the spec is a visual restraint reference, not a primitive migration.
No dedicated shadcn skill was available in this session; the fallback used recorded config/source, pinned runtime, shadcn contracts and official Radix composition / shadcn documentation. Future upstream updates should compare the snapshot before selectively migrating.
Learning is the first migrated consumer. Civic V2/design-system packages and their other consumers remain maintained separately until explicit cutover. Connect, Survey, landing and VTT are not migrated by this prototype.
Checks
build/typecheck, lint, test, and themes:generate are package scripts. Build the actual consumers to verify Tailwind source discovery. lint.css is a tooling-only Tailwind bootstrap for shadcn validation; it is never exported or imported by consumers. Behavioral jsdom fixtures cover keyboard/forms, filtering/paging/selection, themed portals and focus return, sidebar modes, toast pause and repeated dismissal. Pure fixtures cover table models and complete contrast reports/exports. Current preset tests cover all nine themes/both modes, text/focus contrast, references, geometry/density, JSON/CSS round-trips and collision isolation.
Geometry and padding
Lifebound built-ins use pill buttons and segmented tabs (--button-radius), 14px fields and generic controls (--field-radius, --control-radius), 8px badges (--badge-radius), 20px cards and popovers (--panel-radius, --overlay-radius) and 28px bottom sheets (--sheet-radius); menu items inside overlays round concentrically. A theme that leaves button, field, badge or sheet radius unset follows its control or overlay radius. The explicit upstream-reference geometry stays available for comparison. Imported and older saved custom themes preserve their original inherited geometry.
Type roles are Tailwind font-size utilities with their own line height, weight and tracking: text-display (40/800), text-title (30/800), text-heading (20/800), text-card-title (17/700), text-body (16), text-small (14/500), text-label (14/700) and text-meta (13/600).
Density remains the quick height/padding preset. A theme may additionally define padding: { scale, controlPadding, panelPadding, panelPaddingSm, itemPadding }. Scale defaults to 1 and accepts 0.5–2. Each optional role is a base { value, unit: "px" | "rem" } bounded to 0–64px or 0–4rem. Omitted roles inherit from density; explicit roles stay fixed when density changes. All four are multiplied by scale during resolution (resolved maxima 128px / 8rem). ResolvedTheme.padding contains effective dimensions, and padding CSS variables contain effective literal lengths. On auto, inherited padding variables reference the density slots (calc() when scaled) and ResolvedTheme.padding holds the compact starting values. Control padding is horizontal; item padding is vertical for list rows and table cells; panel padding covers interiors and existing Card spacing.
Studio exposes the scale, role overrides, unit conversion and reset under Advanced padding. JSON/CSS round-trips retain authored settings. CSS edits change effective literals and the importer derives their base values under the saved scale. v1 libraries without padding retain their density behavior. Scaling never changes control heights or Tailwind’s global --spacing; consumer className overrides and specialized icon/calendar sizing remain available.
The refined padding presets use 12px control / 20px panel / 14px small-panel / 10px item padding in comfortable mode and 8px / 14px / 10px / 5px in compact mode (at a 16px root). Explicit role overrides and the editor scale remain supported. Default Button labels are 16px semibold; small and extra-small use 14px and 12px. Native control font resets must stay in a base layer so utility typography can apply, including through Learning’s exported layout stylesheet.
Distribution and quality gates
Build emits ESM/declarations under dist; public exports never require consumer TSX transpilation. One graph preserves portal-context identity across subpaths. Components/provider have use-client entry directives; pure theme/core supplies resolution/built-ins without contrast/codecs. The full theme entry provides editor/report APIs. CSS/local font dependencies are packed; component source is included for Tailwind discovery. Workspace Vite consumers use workspace-aliases.mjs for HMR.
Registry releases use public access and the next tag; the repository remains private. pnpm pack builds first; verify:package installs the archive outside the workspace, typechecks every advertised module, imports all Node-safe entry points, builds a Tailwind consumer with local fonts, renders in actual Node SSR and verifies scoped portals. Fixture versions are owned by this package rather than a sibling application. Native Button defaults to type="button"; opt into submission. Disabled/loading asChild removes href, uses aria-disabled, removes tab entry and guards child/Slot activation. Custom children must forward these props/handlers.
See UX contracts, changelog and local quality checks. Screenshots use bundled fonts and pinned Chromium; other OS rendering differences need review, not blind regeneration. SSR import/render is covered; full framework hydration and initial theme seeding remain integration work.
Public beta archives additionally include complete React/theme sources, build configuration/scripts and fixtures, together with LICENSE, LICENSE_CONTENT and NOTICE.md. Source availability does not depend on access to the private repository. Runtime consumers still resolve dist exports; source is for modification and Tailwind class discovery.
