Contributing to AgnosticUI
April 24, 2026 · View on GitHub
Thanks for your interest in contributing! This guide covers the architecture, the local development workflow, and what to verify before opening a PR.
Prerequisites
- Node.js v22 (see
.nvmrc; runnvm useif you have nvm) - Familiarity with the AgnosticUI CLI: see Installation docs
Repo Structure
v2/
├── lib/ Core web components (Lit-based, framework-agnostic)
├── cli/ The agnosticui-cli tool
├── theme-registry/ Design tokens: source of truth for all theming
├── skins/ Pre-built skin themes (skins-bundle.css, skin-switcher)
├── sdui/ Server-Driven UI: schema, renderers, and demo apps
├── examples/ Spot-check environment: react-test, vue-test, lit-test
├── site/ VitePress documentation site
├── playbooks/ AI-prompt-driven page templates
├── docs/ Architecture docs, dev guides, SDUI spec, LLM prompt guide
└── scripts/ Utility scripts (verify, scaffold, build)
v2/lib Architecture
v2/lib is the heart of AgnosticUI. Each component follows a consistent three-layer structure:
v2/lib/src/components/Button/
├── core/
│ ├── _Button.ts # The Lit element class (private, prefixed with _)
│ └── Button.ts # Calls customElements.define('ag-button', ...) and
│ # declares the global HTMLElementTagNameMap entry
├── react/
│ ├── ReactButton.tsx # @lit/react wrapper: bridges Lit events to React props
│ └── index.ts # Barrel: re-exports ReactButton + all public types
└── vue/
├── VueButton.vue # Custom Vue SFC wrapper (not @lit/react; hand-crafted)
└── index.ts # Barrel: re-exports VueButton + public types
Barrel files and custom element registration
Always import from the barrel (react/index.ts or vue/index.ts) rather than directly from the implementation file. Importing the barrel triggers customElements.define as a side effect, ensuring the web component is registered before it is used.
// Correct: barrel import ensures ag-button is registered
import { ReactButton } from './components/ag/Button/react/ReactButton';
// For Vue
import VueButton from './components/ag/Button/vue/VueButton.vue';
Framework-agnostic usage (Svelte, Angular, Solid, etc.)
Other frameworks consume the web component directly. Import core/Button.ts to register the element, then use the <ag-button> tag in your template:
import 'agnosticui-core/components/Button/core/Button';
No wrapper is needed; the web component works anywhere the DOM does.
React wrappers and @lit/react
React wrappers use @lit/react createComponent to map Lit custom events to React-style callback props and handle ref forwarding. If you are adding a new event to a core component, update the React wrapper to expose a corresponding onXxx prop.
Vue wrappers
Vue wrappers are hand-crafted SFCs (not generated by @lit/react). They use defineComponent or <script setup> and emit events in the Vue idiom. The wrapper is also responsible for importing the core file so the custom element is registered.
FACE (Form Associated Custom Elements)
AgnosticUI form components (Input, Checkbox, Select, Toggle, etc.) implement the FACE API so they participate in native <form> submission and constraint validation. This is not widely understood: read the Form Association docs before touching any form component. Getting FACE wrong silently breaks native form behavior.
v2/theme-registry: Design Tokens
Design tokens are the single source of truth for all colors, spacing, typography, and motion values. They live in v2/theme-registry/ and are compiled by Style Dictionary.
The compiled output is pushed into v2/lib/src/styles/:
ag-tokens.css: CSS custom properties for light modeag-tokens-dark.css: CSS custom properties for dark mode
Both files are then imported by components and by the example/playbook projects. If you need to change a color or spacing value, change the token in v2/theme-registry/, recompile, and let the change propagate; do not hardcode values in component CSS.
Full theming documentation: agnosticui.com/theming.html
v2/site: Documentation Site
The docs site is a VitePress project located at v2/site/docs/. VitePress itself is Vue-based, so the custom theme components (v2/site/docs/.vitepress/theme/components/) are Vue SFCs.
StackBlitz embeds
Interactive code examples in the docs use StackBlitz to let readers run real React, Vue, or Lit code in the browser. The embed configuration lives in:
v2/site/docs/.vitepress/theme/components/playbooks-config.ts: maps playbook names to StackBlitz project IDs and settingsv2/site/docs/.vitepress/theme/components/PlaybookStackBlitz.vue: the Vue component that renders the embed
This matters for React examples in particular: the docs site is Vue-based, so StackBlitz is the mechanism that proves React wrappers actually work in a real React context.
v2/playbooks: AI-Prompt Page Templates
v2/playbooks/ contains prompt-driven page templates that demonstrate how to build complete UI workflows (login, onboarding, dashboard, etc.) using AgnosticUI components across React, Vue, and Lit.
Each playbook is noteworthy for two reasons:
-
Codified AI prompts: the
PROMPT-3-FRAMEWORKS.mdfiles show how design intent can be expressed as a reproducible prompt that generates idiomatic code for all three frameworks simultaneously. -
Full theming showcase: every playbook demonstrates the complete theming stack: design-token-based skin switching (
v2/skins/) and dark/light mode toggling. If you add or modify a component, check that it responds correctly to both skin and dark-mode changes, since playbooks are the canonical demonstration of this.
v2/cli: The CLI Tool
The CLI (v2/cli/) is what end users run to scaffold and add components to their projects. See the Installation docs for the user-facing workflow. For contributors modifying the CLI itself, see the CLI Development Cycle section below.
Available commands: init, add, remove, list, sync, storybook, playbook, context (and deprecated view). Run npx agnosticui-cli --help to see the current list.
Component Development Cycle
Open two terminals.
Terminal 1: Build and pack the lib
cd v2/lib
npm run lint
npm run typecheck
npm run test
npm run build
npm pack
# produces agnosticui-core-2.x.x-alpha.x.tgz in v2/lib/
Terminal 2: Install into an example and spot-check
cd v2/examples/react-test # or vue-test / lit-test
npm install /path/to/v2/lib/agnosticui-core-2.x.x-alpha.x.tgz
npx agnosticui-cli sync
npx agnosticui-cli add <ComponentName> --force
npm run dev
# open browser and visually verify the component renders correctly
v2/examples is the official spot-check environment. The example projects are real CLI-installed projects that consume components exactly as end users do, making them a better validation environment and a dogfooding exercise simultaneously.
Running e2e Tests
Each example project has Playwright tests covering all component categories:
cd v2/examples/react-test # or vue-test / lit-test
npm install
npm run e2e
Or run all three via the CI workflow as reference (.github/workflows/verify-examples.yml).
CLI Development Cycle
The example apps in v2/examples/ reference agnosticui-cli by its npm registry version (e.g. "agnosticui-cli": "^2.0.0-alpha.22"), not a file: path. This creates a chicken-and-egg problem: you cannot test local CLI changes via the normal install path without first publishing to npm.
The workaround is to temporarily override the registry reference with a locally-packed tarball, then restore package.json and the lock file before committing.
# 1. Build and pack your local CLI changes
cd v2/cli
npm run build
npm pack
# produces agnosticui-cli-2.x.x-alpha.x.tgz in v2/cli/
# 2. Install the local tarball into the example project
cd v2/examples/react-test # or vue-test / lit-test
npm install ../../cli/agnosticui-cli-2.x.x-alpha.x.tgz
# 3. Run and verify your changes
npx agnosticui-cli <command>
npm run dev
# 4. Restore package.json and lock file: do NOT commit the local override
git checkout -- package.json package-lock.json
Repeat steps 1-4 for each example project you need to verify. This workflow lets you iterate on CLI code locally without polluting git history with temporary file: or tarball references.
Building the v2/lib Reference Tarball
v2/scripts/build-local-tarball.sh builds the component library reference tarball that the CLI copies into scaffolded projects when a user runs npx agnosticui-cli init. It is not a CLI distribution tarball.
If you are working only on v2/lib (components, types, styles), a simpler workflow is sufficient:
cd v2/lib
npm run lint
npm run typecheck
npm run test
npm run build
npm pack
# produces agnosticui-core-2.x.x-alpha.x.tgz in v2/lib/
Use the script when you need the full structured reference tarball (e.g. for a release or to verify the end-to-end scaffolding flow):
cd v2
bash scripts/build-local-tarball.sh
# output: v2/dist/agnosticui-local-v2.x.x-alpha.x.tar.gz
Skip sanity checks with SKIP_CHECKS=true bash scripts/build-local-tarball.sh.
Verification Scripts
node v2/scripts/verify-playbooks.mjs # check playbook health
node v2/scripts/verify-examples.mjs # check example project health
Pass --fix to auto-correct version drift in package.json and agnosticui.config.json.
SDUI Fixture Health Tests
v2/sdui/demo/src/a11y/toHtml.spec.ts runs structural invariant checks on every fixtureBank variation. It uses toHtml() (the same serializer as the a11y tests) to confirm each variation produces the expected element counts and button labels. Assertions are derived from the fixture data itself, so no manual updates are needed when fixtures change intentionally; the tests will catch genuine regressions and pass for correct changes.
These tests run as part of the existing validate-fixtures CI workflow.
PR Checklist
-
npm run lint && npm run typecheck && npm run testpass inv2/lib - Component renders correctly in at least one
v2/examplesproject -
node v2/scripts/verify-examples.mjspasses -
node v2/scripts/verify-playbooks.mjspasses if playbook files were changed - Design token changes originate in
v2/theme-registry/, not hardcoded in component CSS - FACE API is preserved if a form component was modified
-
agnosticui-coredoes not appear as a direct dependency in any examplepackage.json
Code of Conduct
See CODE_OF_CONDUCT.md.