@forthtilliath/forth-ui
v0.5.0
Published
Higher-level, opinionated React components built on top of [`@forthtilliath/shadcn-ui`](../shadcn-ui) primitives. Where shadcn-ui gives you the building blocks (`Button`, `Popover`, `Command`, ...), forth-ui assembles them into complete, batteries-include
Readme
@forthtilliath/forth-ui
Higher-level, opinionated React components built on top of
@forthtilliath/shadcn-ui primitives. Where shadcn-ui gives
you the building blocks (Button, Popover, Command, ...), forth-ui
assembles them into complete, batteries-included patterns — a Combobox
built from Popover + Command, a PasswordInput with a strength meter, a
Dropdrawer that's a DropdownMenu on desktop and a Drawer on mobile —
so you don't have to wire the same recipe from scratch in every project.
62 components across 8 categories, styled with Tailwind CSS and
class-variance-authority, fully typed, and browsable in Storybook
(pnpm run dev:sb from the repo root).
Table of contents
Why forth-ui
- Complete patterns, not just primitives. Every component solves a
whole problem — form field wiring (
Field), file upload with previews (Dropzone,ImageInput), an imperative confirm dialog (useConfirm) — instead of leaving the composition to each consumer. - Consistent variants. Every component that has visual variants uses
the same
class-variance-authorityshape (variant,size,look,shape, ...), so once you've learned one component's API, the others feel familiar. - One folder, one subpath import. No barrel file dragging in the whole
library —
import { Combobox } from "@forthtilliath/forth-ui/components/combobox"pulls in exactly that component's code. - Distributable like shadcn. Every component (but
Grid, see Registry) is also published as a shadcn-compatible registry item, so it can be vendored source-first into a consuming project instead of installed as an opaquenode_modulesdependency.
Installation
Via npm
npm install @forthtilliath/forth-ui@forthtilliath/shadcn-ui (the primitives forth-ui is built on) is a
regular dependencies entry, not a peer — it's installed automatically,
no need to add it yourself.
Import the compiled stylesheet once, alongside your component imports:
import "@forthtilliath/forth-ui/styles/globals.css";It covers forth-ui's components and the shadcn-ui ones they're built on (Dialog, Popover…) — not every shadcn-ui component. If your app also uses others directly (Sidebar, Table…), or has its own Tailwind build anyway, use the Theming setup instead: your Tailwind then scans both packages.
As a workspace package (this monorepo)
Within this monorepo, forth-ui is consumed the same way, just via the
pnpm workspace protocol instead of a published version range:
{
"dependencies": {
"@forthtilliath/forth-ui": "workspace:*"
}
}Components are built to dist/ (./components/* →
./dist/components/*/index.js), so run pnpm run build (or dev) in
this package after source changes for consumers to pick them up.
Via the shadcn registry
Every component also ships as a
shadcn registry item — the source
files land directly in your project (components/forth-ui/<name>/),
import-rewritten to your own components.json aliases, exactly like
npx shadcn add button does for the official shadcn/ui components:
npx shadcn add <registry-url>/combobox.jsonAdd forth-ui as a named registry in your components.json to use the
short form instead:
{
"registries": {
"@forth-ui": "<registry-url>/{name}.json"
}
}npx shadcn add @forth-ui/comboboxCross-component dependencies (e.g. Combobox depending on another
forth-ui component) and shadcn primitives (e.g. Popover) are declared as
registryDependencies and resolve automatically — shadcn add cascades
and installs everything the component needs. See
apps/shadweb for how the registry itself is built.
Usage
import { Field } from "@forthtilliath/forth-ui/components/field";
import { Combobox } from "@forthtilliath/forth-ui/components/combobox";
function Example() {
return (
<Field label="Framework" description="Pick the one you use most.">
<Combobox
options={[
{ value: "react", label: "React" },
{ value: "vue", label: "Vue" },
]}
placeholder="Select a framework..."
/>
</Field>
);
}Language
Built-in labels (aria-labels, default placeholders, empty states, the
Pagination and ConfirmDialog buttons…) are available in French (the
default) and English. For a bilingual site, set the language once with
UiLocaleProvider; a component's own locale prop wins over it, and its
text props (placeholder, emptyMessage, ariaLabel, a ConfirmDialog's
cancelLabel…) still override individual strings.
import { UiLocaleProvider } from "@forthtilliath/forth-ui/locale";
<UiLocaleProvider locale={lang /* "fr" | "en" */}>{children}</UiLocaleProvider>;
// or per component:
<Spinner locale="en" />;UI_MESSAGES (same entry point) holds every label, grouped by component.
Components rendered from shadcn-ui as-is (e.g. a Dialog's close button) keep
shadcn's English text.
Components
Grouped the same way as the Storybook sidebar (forth-ui/<Category>/...).
Layout
| Component | Import | Description |
| ------------------- | ---------------------------- | ----------------------------------------------------------------------------- |
| Accordion | components/accordion | A vertically stacked set of interactive headings that each reveal a section. |
| BentoGrid | components/bento-grid | A CSS-grid layout for unevenly-sized cards ("bento box" layouts). |
| Collapsible | components/collapsible | An interactive component which expands/collapses a single panel. |
| Grid, Grid.Item | components/grid | CSS-grid layout helpers with a built-in debug overlay. |
| InfiniteScroll | components/infinite-scroll | Calls onLoadMore once a sentinel scrolls into view. |
| Marquee | components/marquee | Infinitely scrolls its children (a logo wall, a ticker). |
| Reparent | components/reparent | Moves children between different container elements at different breakpoints. |
| Repeat | components/repeat | Repeats a component a specified number of times. |
| ScrollShadow | components/scroll-shadow | A vertically-scrollable container with fade-out edge shadows. |
| Separator | components/separator | Visually or semantically separates content. |
| Spacer | components/spacer | Adds space between components — a flexible filler or a fixed gap. |
| Stepper | components/stepper | A numbered sequence of steps for a multi-stage flow. |
Navigation
| Component | Import | Description |
| ---------------------------- | ----------------------- | ------------------------------------------------------------------------- |
| Breadcrumb | components/breadcrumb | Displays the path to the current resource using a hierarchy of links. |
| Link, LinkPreview | components/link | A styled hyperlink, plus a hover-triggered metadata preview card. |
| Navbar | components/navbar | A fixed, floating page header that shrinks on scroll, plus a mobile menu. |
| Pagination | components/pagination | Navigates between pages, computing visible page numbers and ellipses. |
| Scrollspy (useScrollspy) | components/scrollspy | Tracks which element id is currently scrolled into view. |
Buttons & Actions
| Component | Import | Description |
| ----------------------------- | ------------------------ | ------------------------------------------------------------------------------- |
| Button, ButtonGroup | components/button | A button, or a component that looks like one, plus joined button rows/columns. |
| Chip | components/chip | A compact, interactive element for status, categories, or filters. |
| ModeToggle, ThemeProvider | components/mode-toggle | A light / dark / system theme switcher (needs the optional next-themes peer). |
Forms
| Component | Import | Description |
| ------------------------- | --------------------------- | ------------------------------------------------------------------------ |
| CheckboxGroup | components/checkbox | A group of checkboxes sharing a single array value. |
| ColorPicker | components/color-picker | A color swatch + hex input, synced with the native color picker. |
| Combobox, MultiSelect | components/combobox | An autocomplete/searchable select, and its multi-value variant. |
| DatePicker | components/date-picker | A single-date picker built on a Popover + Calendar recipe. |
| Dropzone | components/dropzone | A drag-and-drop file upload zone, listing accepted files below. |
| Field | components/field | Wires a label, description and error message to a single form control. |
| ImageInput | components/image-input | A click-to-upload image picker with a live preview. |
| NumberInput | components/number-input | A numeric input with increment/decrement stepper buttons. |
| PasswordInput | components/password-input | A password Input with a show/hide toggle, plus a strength meter. |
| RadioCard | components/radio | A card-style RadioGroup item — the whole card is the clickable target. |
| Rating | components/rating | A star rating control with keyboard navigation and hover preview. |
| SubmitButton | components/submit-button | A submit Button that shows its own loading state via useFormStatus. |
| TagsInput | components/tags-input | A flexible input for adding/removing multiple tags. |
| ToggleSwitch | components/toggle-switch | An on/off switch with an optional visible label and description. |
Feedback
| Component | Import | Description |
| -------------------- | -------------------------------- | ----------------------------------------------------------------------- |
| Alert | components/alert | Displays a callout for user attention. |
| Badge | components/badge | Displays a badge or a component that looks like a badge. |
| Banner | components/banner | A full-width, page-level announcement bar. |
| CountAnimation | components/count-animation | Animates a number counting up (or down) to a value whenever it changes. |
| MultiStepLoader | components/multi-step-loader | A full-screen overlay stepping through loading messages. |
| NotificationCenter | components/notification-center | A bell-icon trigger with an unread badge, opening a notification list. |
| Progress | components/progress | Displays an indicator showing the completion progress of a task. |
| ScrollProgress | components/scroll-progress | A fixed bar tracking scroll percentage of the page or a container. |
| Spinner | components/spinner | An accessible loading indicator, in one of a few visual styles. |
Overlays
| Component | Import | Description |
| ---------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
| CommandPalette | components/command-palette | A global command menu (Cmd/Ctrl+K to open). |
| useConfirm | components/confirm-dialog | An imperative await confirm({...}) alternative to hand-wiring an AlertDialog. |
| CopyMenuItem | components/copy-menu-item | A DropdownMenuItem that copies a value and briefly shows a checkmark. |
| Dropdrawer | components/dropdrawer | A menu that renders as a DropdownMenu on desktop and a bottom Drawer on mobile. |
Data Display
| Component | Import | Description |
| ------------------------------ | ----------------------------- | ------------------------------------------------------------------------ |
| Avatar, AvatarGroup | components/avatar | An image with a fallback, status/badge indicators, and stacked grouping. |
| ColumnMenu (useDndSensors) | components/column-menu | A dropdown to show/hide and reorder (drag and drop) a table's columns. |
| DescriptionList | components/description-list | A description list, with terms and descriptions. |
| FileList | components/file-list | Displays a list of files with name, size, and upload progress. |
| GradientFrame | components/gradient-frame | A visual laid on an offset, gradient-filled frame. |
| IconBubble | components/icon-bubble | An icon inside a round, tinted bubble. |
| Kbd | components/kbd | Displays which key or combination of keys performs a given action. |
| QrCode | components/qr-code | Generates a QR code from a string, rendered as inline SVG. |
| RelativeTime | components/relative-time | Displays a live-updating time, defaulting to the local timezone. |
| Sortable | components/sortable | A drag-to-reorder list built on native HTML5 drag events. |
| ThemeImage | components/theme-image | An image with a light and a dark version (as={Image} for Next.js). |
| Timeline | components/timeline | A vertical sequence of dated events, connected by a line. |
| Tree | components/tree | A hierarchical, expandable/collapsible list of nodes. |
Code & Content
| Component | Import | Description |
| ----------- | ----------------------- | ---------------------------------------------------------------- |
| CodeBlock | components/code-block | A syntax-highlighted code block with a copy-to-clipboard button. |
| Snippet | components/snippet | Displays and copies code in a tabbed interface. |
Conventions
- One folder per component, matching the
exports: "./components/*"map — folders that export more than one related component (Avatar+AvatarGroup,Combobox+MultiSelect,Button+ButtonGroup,Link+LinkPreview) still ship as a single import path. - Variants via
class-variance-authority. Components with visual variants keep them in a co-locatedvariants.ts, exporting both thecva()call and itsVariantPropstype (e.g.BadgeVariants). - Multi-part
className. Components with more than one internal element acceptclassNameas an object instead of a string, keyed by part ({ root?, trigger?, content?, ... }), so you can target exactly the part you need without fighting specificity. fallback/children-first fallbacks, not silent empty states — e.g.Avatar'sfallbackprop is required, not optional.
Theming
forth-ui has no styles of its own beyond Tailwind utility classes and the
CSS variables (--primary, --muted, --border, ...) already defined by
@forthtilliath/shadcn-ui's theme. Theming forth-ui means
theming shadcn-ui — swap the CSS variables (or the whole theme preset) and
every forth-ui component follows.
Setup
In your app's CSS entry point, import the base stylesheet, then a theme
from @forthtilliath/shadcn-ui/styles/themes/*.css — each one just
redeclares the same :root custom properties, so importing it after the
base file overrides the default colors via normal CSS cascade:
@import "@forthtilliath/shadcn-ui/styles/globals-static.css";
@import "@forthtilliath/shadcn-ui/styles/themes/rose.css";
@import "@forthtilliath/forth-ui/styles/sources.css";Don't add your own @import "tailwindcss"; — globals-static.css already
does that internally.
What sources.css does, and why it's needed: Tailwind only generates
CSS for class names it can actually find, as literal text, in the files it
scans — by default your own project source, honoring .gitignore (which is
why node_modules is skipped). But forth-ui and shadcn-ui ship pre-built:
their component code — the literal "bg-primary", "rounded-md", etc.
strings — only exists in their compiled dist/ files. Without registering
them explicitly, none of those classes would be generated and the
components would render unstyled. sources.css does that with
@source
for forth-ui's components and, through shadcn-ui's own sources.css, for
the shadcn-ui ones they're built on.
Its paths are relative to the package itself, so it works whatever the
node_modules layout. Don't write the @source yourself with a glob such as
../node_modules/@forthtilliath/*/dist/**/*.js: under pnpm (packages are
symlinks — junctions on Windows) that pattern silently matches nothing.
Available themes
19 themes ship under
@forthtilliath/shadcn-ui/styles/themes/, all rendered here with the same
components (light and dark mode).
The first 11 are tweakcn-generated — most of them only change the colors:
| Theme | Style | Light | Dark |
| -------------- | -------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- |
| default | shadcn's neutral default: black on white, grey accents. |
|
|
| blue | The default look with a blue primary. |
|
|
| bubblegum | Candy pink and pastels, Poppins, hard offset shadows. |
|
|
| claymorphism | Soft clay: warm greys and indigo, very round, pillowy shadows. |
|
|
| green | The default look with a green primary. |
|
|
| orange | The default look with an orange primary. |
|
|
| red | The default look with a red primary. |
|
|
| rose | The default look with a rose primary. |
|
|
| twitter | Twitter blue, Open Sans, very round corners. |
|
|
| violet | The default look with a violet primary. |
|
|
| yellow | The default look with a yellow primary. |
|
|
The other 8 go further: besides fonts, radius and shadows, each
adds signature effects — plain CSS rules on shadcn-ui's data-slot
attributes (pixel frames, scanlines, frosted glass, hand-drawn outlines…),
active only while the theme is imported. The components themselves are
untouched, and focus stays visible (a theme restyling a button's shadow
gets an outline instead). Their fonts fall back to system stacks; load the
fonts named at the top of each file (Google Fonts, next/font…) for the
full look.
| Theme | Style | Light | Dark |
| ----------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------- |
| brutalist | Neo-brutalism: thick black outlines, hard offset shadows, buttons that press in, dotted paper. |
|
|
| pixel | 8-bit: notched pixel frames, pixel type, checkerboard — Game Boy greens (light), PICO-8 night (dark). |
|
|
| crt | CRT monitor: amber phosphor, scanlines and vignette, glowing text, blinking cursor. |
|
|
| synthwave | Synthwave: sunset sky over a perspective grid, chrome titles, neon gradient buttons. |
|
|
| glass | Glassmorphism: frosted translucent surfaces floating over a colorful gradient mesh. |
|
|
| sketch | Hand-drawn: wobbly inked outlines and handwriting — lined notebook (light), chalkboard (dark). |
|
|
| newspaper | Newspaper: newsprint and ink, Didone headlines over double rules, halftone dots, small caps. |
|
|
| memphis | Memphis pop: confetti pattern, asymmetric corners, squiggly underlined titles, colored hard shadows. |
|
|
Development
pnpm run dev # concurrently: tsc --watch + tailwind --watch
pnpm run build # tsc + tailwind build -> dist/
pnpm run check-types # tsc --noEmit
pnpm run lint # eslintEvery component has a matching Storybook story under
apps/react-sb/src/stories/forth-ui —
run pnpm run dev:sb from the repo root to browse them all, organized into
the same 8 categories as this README.
Registry
apps/shadweb/scripts/generate-forth-ui-registry.ts
regenerates the registry from this package's source — never hand-edit
apps/shadweb/registry/forth-ui/** directly. From apps/shadweb:
pnpm run registry:generate # source -> apps/shadweb/registry/forth-ui + registry.json
pnpm run registry:build # registry.json -> public/r/*.json (shadcn build)
pnpm run registry:sync # both, in orderGrid is intentionally excluded — it depends on
@forthtilliath/react-kit and @forthtilliath/ts-types, external npm
packages that aren't inlined into the registry item yet.
License
MIT
