FiestaUI is the FiestaBoard design system: React components built on Base UI with Tailwind v4 design tokens, published as @fiestaboard/ui.
Component catalog: the Storybook is deployed to GitHub Pages on every push to main.
npm install @fiestaboard/uiNo token, no .npmrc, no registry configuration — the package is public on
registry.npmjs.org and installs anonymously,
in CI and Docker builds as much as locally.
Before 1.6.3 this package lived on the GitHub Packages npm registry, which requires authentication for every read even when the package is public. If you still have a
@fiestaboard:registry=https://npm.pkg.github.comline in an.npmrcanywhere, delete it — it will pin you to the old registry and to versions no longer published there.
Peer dependencies: react / react-dom ^19, lucide-react, and tailwindcss ^4 in the consuming app.
FiestaUI ships no compiled utility CSS. The consuming app runs Tailwind v4 and generates utilities for the class strings inside this package. In your Tailwind entry CSS:
@import "tailwindcss";
@import "@fiestaboard/ui/fonts.css";
@import "@fiestaboard/ui/theme.css";
@source "../node_modules/@fiestaboard/ui/dist";theme.csscarries the design tokens (@theme inline,:root/.darkcustom properties), the base layer, the component animation keyframes, and thedarkcustom variant.fonts.cssis the opt-in Geist font registration (@fontsource-variable/geist+@fontsource-variable/geist-mono@font-facerules, ~52 KB fetched for a Latin-only page). Import it alongsidetheme.cssunless your app supplies Geist itself — e.g. vianext/font, a CDN, or a self-hosted subset. If you self-host, skipfonts.cssand register faces named"Geist Variable"/"Geist Mono Variable"(the namestheme.css's--font-geist-sans/--font-geist-monotokens reference); without either, the tokens degrade gracefully to the system font stack.- The
@sourceline is mandatory — Tailwind v4 does not scannode_modulesby default, and without it component styles silently vanish. Adjust the relative path to wherever your CSS file lives. - Dark mode is class-based: toggle the
darkclass on<html>. FiestaUI only defines the variant; your app owns the toggle.
theme.css inlines only the live season (Pride) — it activates automatically when the app shell stamps .pride-month on <html>. The other seasons are design drafts and are shipped as opt-in stylesheets rather than bundled into every consumer, so you only pay for the ones you use:
@import "@fiestaboard/ui/seasons/christmas.css";Import the file for a season only once you've promoted it (moved its entry into SEASONS and taught the app shell to stamp its htmlClass). All drafts are previewable in Storybook's Season toolbar without any consumer setup.
Migration (breaking): the draft season data (
DRAFT_SEASONS,ALL_SEASONS) is no longer exported from"@fiestaboard/ui"— it's Storybook-only data and now tree-shakes out of production bundles. If you previewed drafts, import them from the subpath instead:import { ALL_SEASONS } from "@fiestaboard/ui/lib/seasons-drafts". The live-season API (SEASONS,getActiveSeason,useActiveSeason, …) is unchanged.
import { Button, Card, CardContent, cn } from "@fiestaboard/ui";
export function Example() {
return (
<Card>
<CardContent className={cn("flex gap-2 p-4")}>
<Button variant="brand">Save</Button>
<Button variant="outline">Cancel</Button>
</CardContent>
</Card>
);
}npm install
npm run storybook # component workbench on :6006
npm run build # dist/ — ESM + d.ts + theme.css
npm run lint && npm run typecheck && npm run format:check
npm run build-storybook && npm run test-storybook # axe a11y sweep (needs the static build served on :6006)This package was extracted from FiestaBoard's web/src/components/ui with a pixel-parity guarantee. Token values in src/styles/theme.css and component class strings are contract, not implementation detail — changes to them are visible in every consumer. Change intentionally and version accordingly (patch: fixes, minor: additive components/variants, major: breaking API or visual changes).
Don't point FiestaBoard's Docker build at an unpublished version. For local iteration:
npm run build && npm pack # produces fiestaboard-ui-<version>.tgz
# in the consumer: npm install /path/to/fiestaboard-ui-<version>.tgzReleases are continuous: every merge to main that changes shipped code bumps the version, tags v<version>, publishes to registry.npmjs.org, and creates a GitHub Release. scripts/release/gate.mjs decides whether a merge earns a release and how big a bump; CI-only changes (workflows, scripts/ci, VRT baselines) mint nothing. Actions → Release → Run workflow remains the manual override.
Publishing uses npm Trusted Publishing (OIDC). The workflow's id-token: write permission mints a short-lived credential that npm exchanges for publish rights, so there is no npm token anywhere — nothing to store, leak, or rotate. Provenance is attested from the OIDC claims, linking each tarball to the workflow run that built it.
This requires a trusted publisher configured on the npmjs package — repository Fiestaboard/FiestaUI, workflow release.yml. Without it the publish step fails outright; it does not fall back to a token.
npm run release:npmjs builds and publishes from a local checkout. It exists as an escape hatch for when the workflow is broken — normal releases should go through CI.
npm login # once — web-based, completes 2FA in the browser
npm run release:npmjs -- --dry-run # inspect the tarball contents first
npm run release:npmjs # publish; completes 2FA interactivelyThe account's 2FA mode is auth-and-writes (check with npm profile get), so the publish needs a second factor. Do not pass --otp unless you have a TOTP authenticator app — with a passkey or hardware security key there is no code to type. With auth-type=web (npm's default) the CLI prints a https://www.npmjs.com/login/<uuid> URL; open it, satisfy the passkey, and the publish continues.
The script rebuilds first because files ships only dist and there is no prepack hook, so a stale dist/ would otherwise publish silently-wrong contents — unrecoverable, since npm forbids republishing a version. Publish from an up-to-date main: the version comes from package.json, so a stale checkout would point npmjs's latest tag at a superseded release.
Every release triggers .github/workflows/downstream-upgrade.yml, which keeps
a single evergreen PR open on Fiestaboard/FiestaBoard (branch
fiestaui-upgrade) pinning @fiestaboard/ui to the newest version. If the
bump breaks FiestaBoard, Claude (Opus) TDD-fixes it (max 3 attempts) and
FiestaBoard CI must pass before the PR is labeled upgrade-green; otherwise
it's labeled upgrade-blocked and the maintainer is pinged. A human on
FiestaBoard always does the merge.
Manual run / backfill: Actions → Downstream Upgrade → Run workflow
(optionally set version; dry_run computes the bump without pushing).
Required repo secrets: CLAUDE_BOT_APP_ID, CLAUDE_BOT_APP_PRIVATE_KEY
(GitHub App with write access to FiestaBoard), CLAUDE_CODE_OAUTH_TOKEN.
Design: docs/superpowers/specs/2026-08-01-downstream-upgrade-design.md.
MIT