shadcn-inspired styling for Foldkit, powered by StyleX.
foldstylex is a small monorepo that turns StyleX design tokens and component styles into named composition primitives so Foldkit apps can build intent-shaped UI instead of anonymous layout soup.
// Prefer named primitives…
Card.section({
title: 'Overview',
children: [
Stack.view({
gap: 'md',
children: [
Row.view({
align: 'between',
children: [
Text.view({ variant: 'sectionTitle', children: 'Pending' }),
Badge.view({ label: '3' }),
],
}),
Stat.card({
label: 'Open items',
state: new Ready({ value: '12' }),
}),
],
}),
],
})
// …not StyleX key salads on every div
// h.div(elAttrs(sxAttrs(h, cardStyles.root, layoutStyles.panelPad)), [ … ])Not a domain UI kit. No product vocabulary (accounting, imports, CPA, …) lives here—only reusable layout, form, data-display, and chrome primitives.
| Layer | Responsibility |
|---|---|
@foldstylex/tokens |
Theme vars (colors, layout metrics) |
@foldstylex/styles |
StyleX rules for components + layout (public escape hatch) |
@foldstylex/foldkit |
Named Html helpers: Button, Card, Stack, Input, Stat, … |
Foldkit / @foldkit/ui |
MVU runtime, headless behavior, a11y |
| Product apps | Domain models, formatting, workflows |
Inspiration, not ports:
- shadcn — slot-style chrome (
Card.header/Card.title) and control variants - daisyUI — short names for common patterns (
stat, stack-like layout) - Tailwind — parametric layout options (
gap,align) instead of dozens of one-off APIs - StyleX — typed, composable styles as data
- Anti–div soup — when a layout pattern is common, give it a name (
Stack,Row,Text,Stat). Residual rawh.divis fine for true one-offs. - Role-based APIs — not every export is
view(config). Layout, controls, form fields, chrome slots, Foldkit stateful widgets, and async display each have a deliberate shape. See AGENTS.md. - Styles stay public — primitives map to StyleX; apps may still compose
layoutStyleswhen needed. Public StyleX keys are also zero-domain. - Effect-first variants — closed unions and async UI state use Schema tagged types
and
Match(e.g. StatLoading|Failed|Ready). - 3+ uses — promote a pattern when it repeats; do not invent speculative mega-APIs.
- Zero domain + generic axes — public APIs expose visual axes (
density,width,align, rowpresentation, celltone). Apps map domain meaning at the boundary. No product vocabulary in the library or in public style keys. - Consumer exports only — package surface is a consumer contract. Tests must not force private mapping helpers into exports. Prefer clean cutover (no shims/aliases) for unpublished internal/demo APIs.
// Library axes — not domain roles
Input.control({ density: 'compact', width: 'sm', align: 'end', … })
Table.tr({ presentation: 'summary', children: [
Table.td({ align: 'right', tone: 'success', children: total }),
]})Full agent rules (TDD, naming, testing bar): AGENTS.md.
System completeness tracking: issue #1.
| Package | Role |
|---|---|
@foldstylex/tokens |
StyleX theme variables |
@foldstylex/styles |
Component + layout StyleX modules, document reset |
@foldstylex/foldkit |
Named primitives + StyleX attribute glue |
examples/sidebar-demo |
Kitchen sink catalog + sidebar demo |
Requires Node >=20.19 or >=22.12, pnpm, and mise
(optional but used for task runners).
pnpm install
mise run dev # kitchen sink + sidebar demo (Vite)
mise run typecheck # tsc across packages
mise run test # vitest
mise run check # full typecheck + tests (always; independent of dirty files)
mise run pre-commit # hk changed-file hooksmise run check is the authoritative full verifier after implementation work.
Open the demo and use the components / kitchen sink views as the living catalog for how primitives compose.
Interactive/MVU changes need Foldkit Story or Scene tests that exercise the real transition. Pure factory tests assert observable structure/a11y/style behavior.
This repo targets Effect v4 (effect@4.0.0-beta.x, same lineage as
effect-smol). Local clone at ../effect-smol
is useful for API exploration. Project guides:
mise exec -- effect-solutions listPrivate / unpublished monorepo unless noted otherwise in package manifests.