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; run nvm use if 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 mode
  • ag-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 settings
  • v2/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:

  1. Codified AI prompts: the PROMPT-3-FRAMEWORKS.md files show how design intent can be expressed as a reproducible prompt that generates idiomatic code for all three frameworks simultaneously.

  2. 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 test pass in v2/lib
  • Component renders correctly in at least one v2/examples project
  • node v2/scripts/verify-examples.mjs passes
  • node v2/scripts/verify-playbooks.mjs passes 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-core does not appear as a direct dependency in any example package.json

Code of Conduct

See CODE_OF_CONDUCT.md.