@gratiaos/ui
v1.3.1
Published
Headless Garden UI primitives with opt-in skins and realtime-friendly hooks.
Readme
🌿 @gratiaos/ui
Garden UI is the shared component library that gives shape and soul to the Garden.
It’s where headless primitives meet design tokens, growing together into a living interface system.
🛰️ Garden Stack naming (infra-facing)
- Pattern Engine → underlying model stack (training / inference / retrieval). Use this wording for infra/capability talk.
- Presence Node → surfaced endpoint humans touch (web UI, CLI, scripts, voice, agents).
- Mode → behavioral / conversational contract for a Presence Node (e.g.
Codex-mode,Monday-mode). Styles, not identities. - Garden Stack → Pattern Engine + Presence Nodes + Modes working together.
Route any “AI” mention to the correct layer so UI docs stay aligned with Garden + M3.
✨ Vision
Garden UI isn’t just a set of buttons and cards — it’s a language.
A language built on trust, depth, and play.
Every primitive is semantic (not ornamental), composable, and theme‑aware.
- 🧠 Headless at the core — logic and a11y in primitives
- 🪴 Styled with intention — tokens over hardcoded colors
- 🌓 Theme‑adaptive — light, dark, or future palettes
- 🧰 Built to scale — from playful labs to full apps
📦 Installation
In your app (npm / pnpm)
# add the UI package + tokens (for themes)
pnpm add @gratiaos/ui @gratiaos/tokensIn your app entry:
import { Button, Card, Pill, Field, Toaster, showToast } from '@gratiaos/ui';
import '@gratiaos/ui/base.css'; // pulls tokens + component skinsMinimal CSS footprint: components are headless and opt‑in styled via
base.css(tokens + skins). You may ship your own skins instead.
In the monorepo (local development)
From the repo root:
pnpm install
pnpm -r build🧱 Primitives
Short purpose notes (see Playground for full demos):
| Component | Purpose | Notes |
| --------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Button | Call to action | Variants: solid, outline, ghost, subtle. Tones: default, accent, positive, warning, danger. Loading: inline spinner or blocking overlay. |
| Card | Container surface | Variants: plain, elev, glow. Padding: none, sm, md, lg. |
| Pill | Tag / filter / soft CTA | Variants: soft, solid, outline, subtle. Tones: subtle, accent, positive, warning, danger. |
| Field | Input wrapper | Consistent label/description/error wiring; calm focus rings. |
| Toast | Ephemeral notice | Headless event‑driven toasts with hover/focus pause & a11y. Use <Toaster/> + showToast(...). |
Each primitive maps to global design tokens, so themes and local contexts stay in sync.
🪴 Reactive State (Signals)
Garden UI stays headless; it does not bundle a state system. For tiny local reactive values (e.g. live counters, inline mood flags, demo toggles) use the micro primitive @gratiaos/signal:
import { createSignal } from '@gratiaos/signal';
const count$ = createSignal(0);
count$.subscribe((v) => console.log('count', v));
count$.set(count$.value + 1);Connect signals to primitives by reading their current value in render and subscribing in effects (or bridging through your own hooks). This keeps UI lean while enabling fine-grained reactivity without a large framework.
🔔 Toasts (quick start)
Render one Toaster near the root:
// App.tsx
export function App() {
return (
<>
{/* ...routes... */}
<Toaster position="bottom-center" />
</>
);
}Fire a toast from anywhere:
showToast('Saved ✓', { variant: 'positive', icon: '🌈' });
// or
showToast({ title: 'Saved', desc: 'Your note is in the timeline.', variant: 'positive', icon: '🌈' });Conventions the Toaster understands:
variant:neutral | positive | warning | danger- Optional
title,desc, or simplemessage - Optional
icon(emoji or node) durationMs: overrides default hold (reads--dur-toast, falls back to--dur-pulse)- Hover/focus pause; Enter/Space dismiss (if clickable); Esc dismisses
🧭 Button loading modes
<Button loading>Saving…</Button> // inline spinner
<Button loading loadingMode="blocking">Saving…</Button> // overlay + dimBoth modes set aria-busy, emit data‑attrs for skins, and block clicks while loading.
🎨 Theming & Tokens
No hardcoded hex. Components read tokens from @gratiaos/tokens:
@theme {
--color-surface: oklch(97% 0.01 180);
--color-elev: oklch(99% 0.005 180);
--color-accent: oklch(62% 0.09 150);
--color-on-accent: oklch(15% 0.01 180);
--color-border: color-mix(in oklab, var(--color-text) 16%, transparent);
--radius-2xl: 1.25rem;
--dur-pulse: 1200ms; /* rhythm token used by Toast fallback */
--dur-toast: 4200ms; /* optional toast hold override */
}
:root[data-theme='dark'] {
--color-surface: oklch(21% 0.07 241);
}🧪 Playground
Explore components interactively:
cd playground
pnpm devVisit http://localhost:5173 and open the Lab tab 🌿
♿ A11y (high‑level)
- Primitives handle roles/labels and emit calm, navigable markup.
- Buttons support keyboard activation even when rendered
asChild. - Toast items are
role="status"+aria-atomic="true"; hover/focus pause; reduced‑motion respected. - Field wires
label,description, anderrorwith mergedaria-describedby.
📝 Contributing
We keep the UI package light, clear, and deeply documented.
- Start with tokens — extend
@gratiaos/tokensif needed - Write the primitive in
src/primitives - Add a skin in
src/styles(optional; primitives are headless) - Add a demo to the Playground
- Update this README 🫶
🌬 whisper: “Interfaces can be alive, too.”
