@xenition/ui
v0.1.3
Published
Official UI kit for Xenition-generated apps — seed-token theme compiler with WCAG auto-contrast, Tailwind preset, themed React primitives (web), and React Native theme tokens.
Maintainers
Readme
@xenition/ui
Official UI kit for apps generated by the Xenition no-code builder. One npm package, subpath exports per concern: a seed-token theme compiler with WCAG-AA auto-contrast, a Tailwind preset bound to CSS variables, themed React primitives (web), a dependency-free motion layer, composed marketing sections, and a resolved token layer for React Native.
The reliability argument that moved backend logic into
@xenition/sdk moves interface
into this kit: the builder LLM wires tested components and writes a ~5-value
theme seed — it does not hallucinate JSX or hand-pick colors.
Install
# Development builds
npm install "github:xenition/ui-kit#develop"
# Production builds
npm install "github:xenition/ui-kit"npm publishing will follow in 1.0.0, alongside @xenition/sdk. react
(>= 18) is a peer dependency.
Seed-token theming
An app's entire look is authored as a tiny theme.seed.json — the only
theme input the LLM (or a settings form) ever writes:
{
"primary": "#7C3AED", // brand color
"accent": "#F59E0B", // optional; defaults to primary hue +40°
"neutral": "warm", // warm | cool | pure — gray ramp temperature
"font": { "heading": "Inter", "body": "Inter" },
"shape": "rounded", // sharp | rounded | pill → radius scale
"mode": "both" // light | dark | both
}compileTheme(seed) is a pure, deterministic, unit-tested function that
derives everything else:
- 11-step color ramps (50–950) for primary, accent, and neutral.
- Semantic slots per mode:
surface,on-surface,primary,on-primary,accent,on-accent,muted,border, and fixed-huesuccess/warn/danger. - Auto-contrast: every
on-X/Xpair is adjusted in lightness until it passes WCAG 2.1 AA (≥ 4.5:1). The adjustment is guaranteed to terminate — a compiled theme can never ship an unreadable button. - Dark mode derived by ramp inversion (50 ↔ 950).
- Radius scale from
shape, plus spacing and typography scales.
Three outputs from one compiled theme:
| Output | Function | Consumer |
| --- | --- | --- |
| CSS custom properties (--xen-*) | toCssVars(theme) | web runtime (injected by the provider) |
| Tailwind preset (var-bound) | toTailwindPreset(theme) | generated web app's Tailwind build |
| Resolved token object | toNativeTokens(theme) | React Native StyleSheet |
Invalid seeds throw descriptive errors — a bad theme is rejected at compile time, never "best-effort applied".
Usage (web)
import { XenitionUIProvider, Button, Card, Input, Stack } from '@xenition/ui';
import seed from './theme.seed.json';
export function App() {
return (
<XenitionUIProvider theme={seed}>
<Card>
<Stack gap="md">
<Input placeholder="Email" />
<Button variant="primary" size="lg">Sign up</Button>
</Stack>
</Card>
</XenitionUIProvider>
);
}// tailwind.config.js
const { compileTheme } = require('@xenition/ui/theme');
const { toTailwindPreset } = require('@xenition/ui/tailwind-preset');
const seed = require('./theme.seed.json');
module.exports = {
presets: [toTailwindPreset(compileTheme(seed))],
content: [
'./src/**/*.{ts,tsx}',
'./node_modules/@xenition/ui/dist/**/*.js', // kit classes must be purge-visible
],
};Because the preset maps bg-primary, text-on-surface, radii, and spacing
to var(--xen-*) references, swapping the injected variables restyles a
built app live — "restyle by prompt" is a data change, not a rebuild.
Motion (@xenition/ui/motion)
Dependency-free scroll and pointer motion — CSS transitions +
IntersectionObserver, no framer-motion. Every component is SSR-safe
(window/observer access is guarded) and honors prefers-reduced-motion:
reduced-motion users get the final state instantly, with no animation.
| Component | What it does |
| --- | --- |
| Reveal | scroll-triggered entrance (effect: fade-up | fade | slide-left | slide-right | zoom | blur-in; delay, duration, once, threshold) |
| Stagger | cascades child Reveals with incremental delays (interval, delay) |
| Parallax | subtle translateY on scroll (rAF + passive listener; speed clamped ±0.5) |
| AnimatedCounter | counts up when scrolled into view (to, from, duration, format) |
| Marquee | infinite horizontal loop (speed px/s, pauseOnHover; duplicate copy is aria-hidden) |
| TiltCard | pointer-tracked 3D tilt (maxTilt, default 8°; off for touch + reduced motion) |
The usePrefersReducedMotion() and useInView() hooks are exported for
custom motion built on the same rules.
Marketing sections (@xenition/ui/marketing)
Composed, slot-based sections for marketing-grade template sites. Styled
only via --xen-* tokens and the var-bound Tailwind preset — no literal
colors anywhere (unit-enforced) — so an entire template restyles from the
theme seed alone, dark mode included.
| Component | What it does |
| --- | --- |
| AuroraBackground | animated layered gradient: blurred ramp-colored blobs (primary/accent 400–700) drifting on slow keyframes; variant: aurora | mesh | radial; optional grain (inline feTurbulence) and pattern: dots | grid |
| GradientHero | full-bleed hero over the aurora; slots: eyebrow, title, subtitle, actions, media |
| Navbar | sticky, backdrop-blur once scrolled, mobile disclosure menu; slots: logo, links (children), actions |
| SectionHeading | eyebrow + title + lede, align left/center |
| FeatureGrid / FeatureCard | responsive feature cards (icon slot, title, body, hover-lift) |
| StatBar / Stat | row of AnimatedCounter stats with prefix/suffix + label |
| Testimonials / Testimonial | quote cards, avatar-initials fallback, mode: grid | marquee |
| PricingTable / PricingTier | tier cards, featured emphasis (token ring/scale + badge), checklists, action slot |
| FAQ / FAQItem | accessible accordion (aria-expanded + labelled region, animated height) |
| CTABanner | compact gradient band reusing the aurora machinery |
| Footer / FooterColumn | multi-column link groups + bottom bar |
| LogoCloud | dimmed logo slots that restore on hover |
| ProductMock | configurable fake-product hero panel (variant: analytics | chat | commerce | calendar; KPI tiles, chart: bars | sparkline | rings | scene, live event feed, pulsing badge, 3D entrance tilt); CSS-only animation, deterministic, aria-hidden |
| BentoGrid / BentoCard | asymmetric span-config feature grid (per-card span/rowSpan, icon tile, metric chip, micro-visual slot, token hover energy wash) |
| ParticleField | ambient particles (mood: ember | snow | fireflies | sparks; density, seed); deterministic golden-ratio layout, static scatter under reduced motion; computeParticles() exported |
| OrnamentRule | editorial divider — fading gradient rule with diamond | dot | line ornament, tone: accent | primary | border |
| PriceList / PriceRow | dotted-leader price rows with ornamented group headings (menus, service lists, rate cards) |
| GenerativeCover | deterministic SVG cover art from { seed, form: arc \| bands \| orbit \| grid \| wave \| stack, ink, paper } — two token color roles, seeded PRNG geometry, no stock imagery |
| PointerHalo | optional custom-cursor affordance — accent halo trailing the pointer, tightens over links, swells with a label over [data-halo="grow"]; fine-pointer only, reduced-motion/touch → renders nothing |
| EditorialGrid / EditorialItem | 12-column asymmetric overlap layout (span/start/offset per item, surface-backed caption slot, automatic z-order so overlaps slide under captions) |
| SectionDivider | section separator (hairline | ornament | fade), optionally parallax-capable via parallax speed |
| EntityCard | generic content/entity card that collapses the templates' bespoke PostCard / ServiceCard / SpeakerCard / ListingCard / ProgramCard — slots { title, eyebrow?, description?, meta?, media?: { imageUrl?, seed?, aspect? }, badge?, footer?, href? }; composes Card + media (<img> or a seeded GenerativeCover) + Eyebrow; one card expresses a blog post, a service, a speaker, a listing, or a program by props alone |
The display primitives that keep recurring across templates live in
@xenition/ui/primitives (also re-exported from the root):
| Component | What it does |
| --- | --- |
| GradientText | ramp-driven clipped gradient text (ramp: primary | accent | primary-accent | accent-primary, angle) |
| Eyebrow | tracked small-caps kicker label (tone, optional flanking rules, align) |
| GlassPanel | color-mix translucent blurred surface over the surface/border tokens (intensity: soft | regular | strong) |
| StatusDot | pulsing semantic status dot (tone: success | warn | danger | primary | accent; echo disabled under reduced motion; optional accessible label) |
| Rating | ★ rating row (value, max=5, size, showValue, label) — filled accent glyphs up to the rounded value, muted after; announced as one role="img" |
| StatusMessage | loading / empty / error feedback (state, optional message) — loading = a reduced-motion-safe CSS spinner + role="status", empty = a muted message, error = a danger-token message + role="alert"; pairs with @xenition/ui/data's useResource |
AuroraBackground is exported directly so templates can build custom
striking sections from the same machinery.
Domain blocks — booking / media / commerce
Presentational components for the module-backed template families. Like the
marketing sections they are token-only (no literal colors — unit-enforced,
both modes verified) and motion/reduced-motion safe, but they carry no
data layer: every component takes its data as props (the shapes below
mirror the SDK module DTOs) and never fetches or imports an SDK. A template
wires them to @xenition/ui/data + @xenition/sdk; the kit just renders.
Booking (@xenition/ui/booking)
Prop shapes mirror the booking module — a resource is
{ name, timezone, slotMinutes } and a slot is
{ startsAt, endsAt, spotsLeft } (instants are ISO-8601 strings).
| Component | What it does |
| --- | --- |
| BookingCalendar | month or week view highlighting days with availability (derived from slots, or a per-day availability summary, bucketed in timezone); ARIA grid with roving-tabindex cells and full keyboard nav (arrows/Home/End/PageUp-Down/Enter), availability shown as a dot and in the aria-label; selectedDate, onSelectDate |
| SlotPicker | grid of bookable times for a day; slots, onPick(slot), selected, a formatTime override (default timezone-aware), remaining-spots hint, and disabled/Full when spotsLeft === 0 |
| BookingSummary | read-only recap of the chosen resource + slot (date, time range, duration, timezone), optional action slot |
Media (@xenition/ui/media)
Prop shapes mirror the media module — an album { title } and items
{ url, kind, caption, alt, width, height }.
| Component | What it does |
| --- | --- |
| Gallery | responsive grid (+ masonry variant) of media items; columns, onOpen(index), loading="lazy" images, aspect-ratio boxes reserved from width/height |
| Lightbox | fullscreen overlay viewer; items, index (null = closed), onClose/onPrev/onNext, loop; role="dialog" aria-modal, focus trap (enter on open, cycle on Tab, restore on close), keyboard (Esc/←/→), token-styled backdrop, opacity-only fade dropped under reduced motion |
| MediaFigure | a single item with its <figcaption>, in a reserved aspect-ratio box |
Commerce (@xenition/ui/commerce)
Prop shapes mirror the catalog/cart/order module — Product { slug, title,
imageUrl }, Variant { title, priceCents, currency, compareAtCents },
CartItem { title, variantTitle, quantity, unitPriceCents, imageUrl }. Money
is always integer cents, formatted through the single exported
formatMoney(cents, currency) home (overridable per component via a
formatMoney prop).
| Component | What it does |
| --- | --- |
| formatMoney | the one cents → localized currency string util (1200 → "$12.00") |
| PriceTag | formatted price with an optional struck-through compareAt |
| ProductCard | media (image, or a seeded GenerativeCover fallback when no imageUrl), title, PriceTag, optional onAdd/href |
| ProductGrid | responsive grid of ProductCards (columns) |
| QuantityStepper | −/n/+ control with min/max clamping (boundary button self-disables), onChange |
| CartLineItem | thumbnail, title + variant, QuantityStepper, line total (unit × qty), remove |
| CartSummary | subtotal / shipping (Free at 0) / tax / discount / total rows (all cents), onCheckout |
| OrderSummary / CheckoutSummary | read-only order recap — line items + totals + StatusBadge |
| StatusBadge | order-status pill using contrast-checked X/on-X semantic pairs |
| EmptyState | generic empty / no-results (empty cart, no matches) — icon slot, title, description, action |
Composition, not creation
The kit owns the design; apps only compose. Every "bespoke" visual trick the
flagship templates shipped — the fake dashboard product shot, the bento
grid's hover wash, the ember sky, brass rules and dotted price leaders, the
generative cover plates, the studio cursor, the overlapping editorial grid —
now lives here as a configurable, token-pure, reduced-motion-safe component.
The same rule extends to the domain blocks: a booking flow, a photo
gallery + lightbox, a storefront grid, a cart drawer, or an order recap is
composed from @xenition/ui/booking · /media · /commerce, never
hand-written per template. It also extends to the per-template card zoo:
the bespoke PostCard / ServiceCard / SpeakerCard / ListingCard /
ProgramCard each template used to hand-build now collapse into a single
token-only EntityCard (marketing, web + native), the Stars widget into
Rating, and hand-rolled loading/empty/error blocks into StatusMessage —
one contract, restyled by seed, contrast-checked, reduced-motion-safe. Those components take data as props (no fetching,
no SDK import) and are styled by tokens alone, so the same booking calendar or
product grid restyles — light and dark — from the theme seed.
Templates and the no-code builder must compose these components (and the
/motion layer) rather than writing their own motion or color CSS.
Bespoke visual code in a generated app is an anti-pattern: it can't be
restyled by seed, isn't contrast-checked, and won't respect reduced motion.
If a design needs something the kit can't express, the fix is a new prop or
component in the kit — never a one-off in the app.
Building a beautiful template
The showpiece pattern — an aurora hero, staggered reveals, and a counting stat bar — is pure composition; not a single color is written:
import { XenitionUIProvider, Button } from '@xenition/ui';
import { Reveal, Stagger } from '@xenition/ui/motion';
import {
GradientHero,
SectionHeading,
FeatureGrid,
FeatureCard,
StatBar,
Stat,
CTABanner,
} from '@xenition/ui/marketing';
import seed from './theme.seed.json';
export function LandingPage() {
return (
<XenitionUIProvider theme={seed}>
<GradientHero
variant="aurora"
grain
pattern="dots"
eyebrow="Now in beta"
title="Launch a themed site in minutes"
subtitle="Every color on this page comes from a 5-value theme seed."
actions={
<>
<Button size="lg">Get started</Button>
<Button size="lg" variant="secondary">Live demo</Button>
</>
}
/>
<SectionHeading align="center" eyebrow="Features" title="Why it works" />
<Stagger interval={120}>
<Reveal effect="fade-up"><FeatureCard title="Token-driven">Restyle by seed.</FeatureCard></Reveal>
<Reveal effect="fade-up"><FeatureCard title="Accessible">WCAG-AA by construction.</FeatureCard></Reveal>
<Reveal effect="fade-up"><FeatureCard title="Fast">No animation library.</FeatureCard></Reveal>
</Stagger>
<Reveal effect="zoom">
<StatBar>
<Stat to={12000} suffix="+" label="Sites generated" />
<Stat to={99} suffix="%" label="Lighthouse a11y" />
</StatBar>
</Reveal>
<CTABanner title="Ready?" action={<Button size="lg">Start free</Button>} />
</XenitionUIProvider>
);
}Swap the seed, and the aurora, buttons, rings, and patterns all follow — "restyle by prompt" now covers the marketing site, not just the app shell.
Usage (React Native)
The token layer alone is still available for bespoke styling —
useXenitionTheme() returns resolved hex/px values (RN cannot read CSS
variables):
import {
XenitionNativeThemeProvider,
useXenitionTheme,
} from '@xenition/ui/native/theme';
function BuyButton() {
const { colors, tokens } = useXenitionTheme();
return (
<Pressable
style={{
backgroundColor: colors.primary,
borderRadius: tokens.radius.md,
padding: tokens.spacing.md,
}}
>
<Text style={{ color: colors.onPrimary }}>Buy</Text>
</Pressable>
);
}But the kit now ships genuine React Native components under
@xenition/ui/native/* that mirror the web prop contracts exactly — a
template's mobile screen composes them 1:1 like the web page, swapping
web→native by import path only (onClick → onPress is the one idiomatic
change). They are real RN components (View / Text / Pressable /
TextInput / Image / FlatList / Animated — no DOM), styled only
from the compiled theme tokens (light + dark, restyle-by-seed), and
reduced-motion-safe via AccessibilityInfo.
import { XenitionUIProvider, Button, Card, Stack } from '@xenition/ui/native/primitives';
import { ProductGrid, ProductCard, CartSummary } from '@xenition/ui/native/commerce';
import seed from './theme.seed.json';
export function Storefront() {
return (
<XenitionUIProvider theme={seed}>
<ProductGrid columns={2}>
<ProductCard title="Ceramic Mug" priceCents={2400} compareAtCents={3200} onAdd={add} />
<ProductCard title="Linen Napkin" priceCents={1800} imageUrl={url} />
</ProductGrid>
<CartSummary subtotalCents={4200} shippingCents={0} taxCents={336} totalCents={4536} onCheckout={checkout} />
</XenitionUIProvider>
);
}@xenition/ui/native/primitives
XenitionUIProvider (native root), Button (variant primary/secondary/ghost,
size sm/md/lg, onPress, disabled, loading), Card, Stack
(direction/gap/align, plus an additive justify), Input
(invalid + optional label), Eyebrow, StatusDot (Animated pulse,
reduced-motion-safe), Rating (★ row — same value/max/size/showValue/
label contract as web), StatusMessage (loading ActivityIndicator / empty
muted / error danger feedback — same state/message contract as web),
GlassPanel (translucent rgba derived from the surface token),
GradientText, EmptyState, PriceTag, and formatMoney (re-exported from
the single commerce home).
Gradients on native. RN has no
background-clip: text, soGradientTextuses a tasteful solid-token fallback (the ramp's mid step) rather than pulling in a masked-view dependency; theangleprop is accepted for parity but inert. Real gradient surfaces (the commerce cover placeholder) useexpo-linear-gradient— an optional peer that every Expo consumer already has; absent it, those surfaces degrade to a solid token fill.
@xenition/ui/native/commerce
formatMoney, PriceTag, ProductCard, ProductGrid (FlatList-backed),
QuantityStepper, CartLineItem, CartSummary, OrderSummary /
CheckoutSummary, StatusBadge, EmptyState, and a native GenerativeCover
image placeholder — all matching the web @xenition/ui/commerce prop APIs.
Money is integer cents throughout.
react-native is a peer dependency; expo-linear-gradient is an optional peer
(used for gradient cover placeholders).
@xenition/ui/native/marketing
EntityCard — the native mirror of the web marketing EntityCard, with the
same slot contract (href→onPress is the idiomatic swap). Composes the native
Card + media (Image, or a seeded GenerativeCover) + Eyebrow, so one card
expresses a blog post, a service, a speaker, a listing, or a program by props
alone — mobile screens compose it 1:1 with the web page.
@xenition/ui/native/booking
BookingCalendar (View/Pressable month/week grid — availability dots,
selected/disabled cells, accessibilityState; header chevrons change month),
SlotPicker (FlatList of time chips — full slots disabled with a Full
label, low-stock "{n} left" hint, onPick), and BookingSummary (resource +
slot recap card) — all matching the web @xenition/ui/booking prop APIs
(onSelectDate/onPick are the native event idioms). The pure date helpers
(toDayKey/dayKeyInTz/monthMatrix/weekRow/…) are re-exported from the
single web home, never duplicated.
@xenition/ui/native/media
Gallery (FlatList grid — numColumns, grid/masonry from
width/height via the RN aspectRatio style, windowing gives the web's lazy
loading; onOpen(index)), Lightbox (a reduced-motion-safe transparent RN
Modal — token-styled backdrop, prev/next Pressable controls, and horizontal
swipe via PanResponder from RN core with no extra gesture dependency; the
Android back button routes through onRequestClose → onClose; renders nothing
for a null/out-of-range index), and MediaFigure (single image + caption) —
all matching the web @xenition/ui/media prop APIs (onOpen/onActivate/
onClose/onPrev/onNext are the native event idioms).
@xenition/ui/native/motion
Reveal ({children, effect: 'fade' | 'fade-up' | 'zoom', delay?, duration?}) and
Stagger (sequences child Reveal delays), built on the RN Animated API only
(no animation library). On mobile the norm is a mount entrance, so Reveal
animates in on mount rather than on scroll; under the OS "Reduce Motion" setting
content renders immediately with animations off. The scroll/pointer-driven web
pieces — Parallax, Marquee, TiltCard, AnimatedCounter — are web-only
(they depend on scroll position / pointer events / IntersectionObserver with no
React Native analogue); use them from @xenition/ui/motion in web templates.
Subpath layout
| Subpath | Contents |
| --- | --- |
| @xenition/ui | everything web: theme + primitives + XenitionUIProvider |
| @xenition/ui/theme | compileTheme, color/WCAG utilities, output functions |
| @xenition/ui/tailwind-preset | toTailwindPreset (also the default export) |
| @xenition/ui/primitives | Button, Card, Input, Stack, GradientText, Eyebrow, GlassPanel, StatusDot, Rating, StatusMessage |
| @xenition/ui/motion | Reveal, Stagger, Parallax, AnimatedCounter, Marquee, TiltCard |
| @xenition/ui/data | useResource hook + headless Resource (loading/error/empty branching) — pairs with @xenition/sdk/client |
| @xenition/ui/marketing | GradientHero, AuroraBackground, Navbar, FeatureGrid, PricingTable, FAQ, ProductMock, BentoGrid, ParticleField, GenerativeCover, EditorialGrid, PointerHalo, EntityCard, … |
| @xenition/ui/booking | BookingCalendar, SlotPicker, BookingSummary (+ toDayKey / dayKeyInTz / formatTimeInTz date helpers) |
| @xenition/ui/media | Gallery, Lightbox, MediaFigure |
| @xenition/ui/commerce | formatMoney, PriceTag, ProductCard, ProductGrid, QuantityStepper, CartLineItem, CartSummary, OrderSummary / CheckoutSummary, StatusBadge, EmptyState |
| @xenition/ui/native/theme | XenitionNativeThemeProvider, useXenitionTheme |
| @xenition/ui/native/primitives | XenitionUIProvider, Button, Card, Stack, Input, Eyebrow, StatusDot, Rating, StatusMessage, GlassPanel, GradientText, EmptyState, PriceTag, formatMoney (React Native) |
| @xenition/ui/native/commerce | formatMoney, PriceTag, ProductCard, ProductGrid, QuantityStepper, CartLineItem, CartSummary, OrderSummary / CheckoutSummary, StatusBadge, EmptyState, GenerativeCover (React Native) |
| @xenition/ui/native/marketing | EntityCard (React Native; mirrors the web marketing EntityCard, href→onPress) |
| @xenition/ui/native/booking | BookingCalendar, SlotPicker, BookingSummary (React Native; shares the web date helpers) |
| @xenition/ui/native/media | Gallery (FlatList), Lightbox (RN Modal + PanResponder swipe), MediaFigure (React Native) |
| @xenition/ui/native/motion | Reveal (mount entrance via Animated, reduced-motion-safe), Stagger (React Native) |
The booking / media / commerce families ship these as presentational
components (data-as-props, no fetching); the SDK-backed headless hooks that
feed them — wired through one root provider, à la @xenition/ui/data — land
alongside their SDK modules. Further families (@xenition/ui/catalog, …)
follow the same pattern as their modules land.
Relation to @xenition/sdk
@xenition/ui is the interface half of the same moat: components are
SDK-backed and will accept the app's XenitionClient through the root
provider, so LLM wiring stays "provider once + drop components". The two
packages version together and are injected into generated apps the same way.
Repository convention (private → public mirror)
This repo (xenition/ui-kit-private) is the source of truth where all work
happens; CI mirrors it to the public xenition/ui-kit on push — the same
convention as node-sdk-private → node-sdk. Unlike the SDK there is no
URL patching in the mirror step: the kit embeds no backend URLs (it receives
the client), so the split is purely source-visibility policy.
Development
npm install # also builds (prepare)
npm test # jest (ts-jest)
npm run build # tsc → dist/Hard rule (enforced by lint in CI): kit components consume tokens only — no literal colors.
License
MIT
