ui
Shared UI component library for Firna React Native and web surfaces. The first consumers are the accounting app and the Juno app.
Key Features
- Shared dropdown menu, selector, combobox, drag-select, segmented control, radio card, switch, spinner, loaders (six indeterminate shapes plus determinate progress bar and ring), button, labelled input/textarea, data table, editable data grid (Airtable/Notion-style), a from-scratch interactive chart family (bar, line, area, sparkline, stat tile, donut, gauge, bullet, funnel, matrix heatmap, scatter, histogram, waterfall, small multiples) on a colourblind-validated palette, cross-platform block rich-text editor with canonical markdown and a collaboration layer (live carets, tracked changes, comment threads), modal, toast provider/controller, avatar, status badge, animated comet-trail border, calendar heatmap, full event-calendar (month/week/day/agenda, recurring events, drag-to-create), branching workflow-builder step-graph primitives, a multi-track editing timeline with pro clip edits, and the video-editor panels that surround it (program monitor, transport, level meter, media bin, property inspector, effects rack, keyframe editor, export dialog).
- A shared
xs/sm/md/lgsize scale (ControlSize) across the interactive controls — buttons, inputs, dropdown selectors, date fields, segmented controls, switches, and list rows.xsis the dense 26px size. - Touch surfaces: inside a
TouchSurface, the controls and list rows give each target at least 44 by 44px, whatever their size. An optional compact text scale serves dense surfaces. - App shell parts: link buttons and link tabs with current-page state, neutral and urgent notices, page and panel empty states, menus drawn open in place with a phone menu sheet, and a controlled panel resize handle.
- Themeable visual tokens so consumers can use their own brand primary color, with four shipped presets — the accounting default and Juno, each in a light and a dark variant. Every component reads its colors from the tokens, so dark mode is a preset swap rather than a per-component opt-in.
- A shared, calm focus glow across every control. On web the DOM backend paints
it from CSS
:focus-visible, so server-rendered and static HTML keeps the same keyboard affordance before hydration — or with no JavaScript at all. Native keeps its platform focus behavior. OnefocusIndicatorsetting — on the theme for every control, or per instance — picks the glow (ring), the browser outline on the control's visible box (outline), or no focus styling at all (none) for a control embedded in a surface that shows focus itself. - Portaled, anchored web date/dropdown/popover overlays, each placed by its own measured size so it flips above its trigger rather than being clipped, with content-sized selector menus and z-index escape hatches, plus touch-friendly native date sheets.
- Stacked web dialogs with focus control for the top dialog, ordered focus return, a page scroll lock, and live toast feedback, plus native dialogs and bottom sheets.
- Platform files shared by Expo, React Native, and the library's own DOM backend on web.
- Focused unit tests, browser interaction tests, and package export checks.
- Storybook previews for visual review on same-repository non-release PRs.
- Release-please release PRs and npm trusted publishing for
@firna/ui.
User-Facing Interface
The package name is @firna/ui. Public exports are available from:
@firna/uifor all public components and helpers.@firna/ui/animated-borderfor the animated comet-trail border that traces a rounded-rectangle perimeter to highlight an element, in a single color or a two-color brand gradient.@firna/ui/avatarfor the themed initials avatar (circle or rounded square).@firna/ui/badgefor the themed status badge pill with tone, variant, and size variants.@firna/ui/buttonfor the themed button with tone, size, and block variants, and arolethat re-points it at another single-activation role (checkbox,menuitem,radio,switch,tab) with the state that role must carry — so a tab or checkbox keeps the shared focus glow instead of being hand-rolled.@firna/ui/calendarfor the full event calendar (month, week, day, and agenda views, recurring events, and drag-to-create).@firna/ui/chartfor the interactive chart family — bar, line, area, sparkline, stat tile, donut, gauge, bullet, funnel, matrix heatmap, scatter, bubble, histogram, waterfall and small multiples — built from this library's own primitives andreact-native-svg, with no charting dependency. Every chart ships a keyboard-navigable hit layer, a hover/scrub readout and an accessible data-table twin.@firna/ui/data-gridfor the editable Airtable/Notion-style data grid (cell-range selection, keyboard nav, virtualized infinite scroll, typed editable cells, column menus, and a responsive card stack).@firna/ui/datefor single-date and date-range fields.@firna/ui/drag-selectfor rubber-band selection of rows and tiles: a provider that can wrap one list or every page of an app shell, theuseDragSelectableOptionspage hook, target and state hooks, controlled (selectedIds) or uncontrolled selection, a selection scope per page, configurable start rules and an ignore marker, edge auto-scroll, a custom box throughrenderMarquee, and geometry helpers. The gesture runs on web; native keeps the selection state.@firna/ui/dropdownfor dropdown menu, selector, combobox, layer helpers, and selector trigger refs for imperative focus after async form hydration.@firna/ui/empty-statefor page and panel empty states with a heading level supplied by the caller.@firna/ui/heatmapfor the calendar contribution heatmap and its pure layout and color-scale helpers.@firna/ui/inputfor the labelled text input, textarea, and bare input frame.@firna/ui/kanbanfor the horizontally scrolling status board that groups records into columns by a single-select field.@firna/ui/linkfor link buttons and link tabs with destinations and current-page state.@firna/ui/listfor the vertical list with between-item separators, optional clickable items, link navigation rows, and theListItemrow.@firna/ui/loaderfor the loading indicator family:Loaderwith six interchangeable indeterminate shapes (ring,dot-grid,dots,bars,blades,pulse), plusProgressBarandProgressRingfor work whose total is known.@firna/ui/modalfor the shared dialog frame with automatic web stacking and native modal presentation, plus the public portal, model, and layer helpers.@firna/ui/noticefor neutral and urgent notices with body and action slots.@firna/ui/panel-resizefor the controlled panel resize handle.@firna/ui/popoverfor anchored popovers, in-place menus, and phone menu sheets.@firna/ui/radiofor themed titled radio-option cards.@firna/ui/rich-textfor the Notion-style block editor with markdown in/out, inline formatting, lists/checklists, native keyboard toolbars, and the collaboration layer (live carets, tracked changes, comment threads) with itsRichTextPresenceBarandRichTextCollabRailsurfaces.@firna/ui/segmentedfor themed single-select segmented controls.@firna/ui/sheetfor the modal bottom sheet that hosts a form, a menu list, or a picker behind one controlled API.@firna/ui/skeletonfor content-shaped loading placeholders: a shimmering bar and circle, a row and column group, and the pulse provider that sweeps a whole skeleton off one animation.@firna/ui/sortable-listfor the drag-and-drop sortable list (pointer + keyboard reordering, an optional start/end grab handle, and vertical or horizontal flow), plus theSortableGroupscoordinator that lets several lists exchange items — stacked as sections or laid out as a board — reporting each move with its source and destination group.@firna/ui/spinnerfor the themed indeterminate spinning loading indicator.@firna/ui/status-dotfor the small themed status dot that signals state beside a label and can pulse while the state is live.@firna/ui/switchfor themed binary on/off switches.@firna/ui/tablefor the data table with optional headers and clickable rows.@firna/ui/themeforSharedUiThemeProvider, default accounting-style tokens, the Juno token preset, thedarkSharedUiThemeandjunoDarkSharedUiThemedark presets, theSharedUiSchemetype,createSharedUiTheme(overrides, base), and the theme-widefocusIndicator(SharedUiThemeProvider theme={{ focusIndicator: "outline" }}swaps every control's focus glow for the browser outline at once). See Theming for the dark-mode contract and Focus indicators for the focus modes.@firna/ui/focusRingforuseFocusRing,focusRingStyleFor,focusRingCssVariablesFor, andfocusRingDomPropsFor. See the focus indicators guide for custom controls.@firna/ui/timelinefor the multi-track editing timeline: the ruler, lanes, clips, and playhead of a video or audio editor.@firna/ui/toastfor the toast provider, theuseToasthook, thetoastControllermethod API, and transient notification toasts including card and solid variants with optional custom leading icons.@firna/ui/touch-surfaceforTouchSurfaceanduseTouchSurface, which mark and read surfaces with 44px control targets.@firna/ui/typographyfor text roles,TextScaleProvider,useTextScale, and the per-textscaleoverride.@firna/ui/video-editorfor the video-editor panels around the timeline: program monitor, transport, level meter, media bin, property inspector, effects rack, keyframe editor, and export dialog.@firna/ui/workflowfor the branching workflow builder — a step-graph canvas (color-coded nodes, tinted edge labels, forks, legend, add-step) for constructing automation workflows.
Installation
npm install @firna/ui
All platform packages are optional peer dependencies; install the set for your target:
- Web only (Vite, Next.js, any DOM bundler):
react,react-dom, andlucide-react— that is the whole web peer set. Noreact-nativeand noreact-native-webinstall, and no bundler alias is needed: theimportcondition resolves todist/node, where every primitive renders through the library's own DOM backend. That holds for types too — the web build's declarations are self-contained, so even a strict TypeScript consumer installs nothing extra. - Expo / React Native (iOS, Android, and Expo web):
react,react-dom,react-native,react-native-svg,lucide-react-native, andlucide-react. Metro'sreact-nativecondition resolvesdist/**, where platform files pick native or web implementations per file. An Expo web app keeps whateverreact-native-webits own React Native code needs; the library's.webfiles resolve to the DOM backend there too and never reach for it. - Optional on native:
@gorhom/bottom-sheet,react-native-gesture-handler, andreact-native-reanimatedpower the native bottom sheet.
Every component reaches the platform through src/primitives, a small set of
modules with a native file and a .web sibling: React Native primitives
(react-native on native; the library's own DOM backend on web), SVG
(react-native-svg on
native, DOM <svg> on web), and icons (lucide-react-native on native,
lucide-react on web). Icon props accept IconComponent, a type both Lucide
packages' icons satisfy, so a consumer passes whichever matches their
platform.
Theming
See the theming guide.
Focus indicators
See the focus indicators guide.
Web focus CSS and server rendering
See the focus indicators guide.
Chart colors
See the theming guide.
Developer Get Started
npm ci
npm test
npm run typecheck
npm run typecheck:web
npm run build
npm run test:dist
npm run test:package
npm run storybook
npm run storybook:build
npm run test:browser
The development guide
explains each check, the complete cargo xtask check gate, the AI review
wrapper, the mockup catalogue, the browser and snapshot suites, and the package
export map.
Package Releases
See the releases and deployments guide.
Storybook Deployments
See the releases and deployments guide.
Key Code Jumping Points
- Platform seam (React Native, SVG, icons): src/primitives
- Shared theme boundary: src/theme.tsx
- Component family READMEs: each family entry under User-Facing Interface links its README.
- Shared control-size scale: src/controlSize.ts
- Touch surface marker and its 44px target size: src/touchSurface.tsx, and the touch metrics in src/touchMetrics.ts
- Shared focus-glow primitive: src/focusRing.ts, its marker spellings in src/focusRingHost.ts, and the CSS rules in src/primitives/dom/css.ts
- Guides: theming, focus indicators, development, and releases and deployments
- Browser tests: tests/browser/, one Playwright spec for each component family
- Story visual/ARIA snapshot sweep: tests/browser/snapshots.spec.ts
- Repository automation: xtask/README.md
- Shared component protocol and its linked pages: docs/protocol/shared-ui-components.md
- App shell parts protocol, for the
xssize, compact text, touch surfaces, links, notices, empty states, menus, and panel resizing: docs/protocol/app-shell-parts.md - Mockup catalogue: docs/mockups/README.md
- Web overlays protocol: docs/protocol/web-overlays.md
- Rich-text collaboration protocol: docs/protocol/rich-text-collaboration.md
- Drag select protocol: docs/protocol/drag-select.md, its selection rules, and its option reference
- Consumer migration handoff: docs/consumer-migration.md
Related Repositories
- Accounting consumer/source components:
/Users/calummoore/projects/futex/accounting - Juno consumer:
/Users/calummoore/projects/futex/juno