Accretion UI is a cross-framework component system built from one core web component library and distributed as independent framework packages.
For product and design leadership, the core proof point is simple: one implementation can ship a consistent button experience across React, Angular 18-20, and Angular 21+ without duplicating component logic.
If you only have a few minutes, open these first:
| Framework Target | Storybook | Chromatic Project | npm Package |
|---|---|---|---|
React (@accretion_ui/react) |
View Storybook | View Chromatic | @accretion_ui/react |
Angular 18 (@accretion_ui/angular_18) |
View Storybook | View Chromatic | @accretion_ui/angular_18 |
Angular 21 (@accretion_ui/angular_21) |
View Storybook | View Chromatic | @accretion_ui/angular_21 |
Core package (single source of truth): @accretion_ui/core
- Accretion UI Figma library: Accretion UI (Figma)
- Current status: the Figma library is currently empty.
- Component styles are taken from the tokens folder files like:
/tokens/primitives.json/tokens/semantic/button.json/tokens/semantic/accordion.json
- Same component API across frameworks.
- Same visual intent hierarchy (
primary,secondary,tertiary). - Same behavior for hover, active, focus, and disabled states.
- Same slotted content behavior and clickable interactions.
- Framework-native state updates driven by the same underlying web component.
- Start Here (2-Minute Demo)
- Figma Design Source
- What to Look For in the Demos
- Support Matrix
- Packages
- How It Works (Single Core, Multiple Frameworks)
- Install
- Framework Setup Examples
- Testing Strategy and Setup
- Smoke Testing (How It Works)
- Storybook and Chromatic Workflow
- Release and Publish Workflow
- Publish Command Reference
- Known Bugs
- Contributing
- Repository and Branch Map
- Package and Chromatic Repositories
- Project Layout
- Coming Soon (TODO)
| Area | Oldest Supported | Newest Supported |
|---|---|---|
| Angular wrappers | Angular 18.x (@accretion_ui/angular_18) |
Angular 21.x (@accretion_ui/angular_21) |
| React wrapper | React 18.2.0 |
React 19.x |
| Angular package split | @accretion_ui/angular_18 for >=18 <21 |
@accretion_ui/angular_21 for >=21 <22 |
| Package | Purpose | npm |
|---|---|---|
@accretion_ui/core |
Source of truth (core Stencil web components) | npm |
@accretion_ui/react |
React wrapper (v18+) | npm |
@accretion_ui/angular_18 |
Angular wrapper (v18-v20) | npm |
@accretion_ui/angular_21 |
Angular wrapper (v21+, includes signals) | npm |
Accretion UI uses Stencil to compile standards-based web components and generate framework wrappers.
Key Stencil references:
Flow:
- Components are authored once in
components/core. - Stencil builds
@accretion_ui/core. - Stencil output tooling generates React and Angular wrappers.
- Wrappers publish as standalone npm packages.
- App teams consume wrappers directly with normal package installs. No app-side code generation is required.
npm install @accretion_ui/reactnpm install @accretion_ui/angular_18 @accretion_ui/corenpm install @accretion_ui/angular_21 @accretion_ui/coreFor server-rendered or statically generated applications, make sure this CSS is present in the initial server response so unresolved custom elements stay hidden until the browser defines them:
accretion-accordion:not(:defined),
accretion-accordion-item:not(:defined),
accretion-accordion-header:not(:defined),
accretion-accordion-trigger:not(:defined),
accretion-accordion-panel:not(:defined),
accretion-button:not(:defined) {
visibility: hidden;
}Apply that rule in a global stylesheet or root layout/document <head> that is loaded with the first HTML response. Do not rely on component-scoped CSS, CSS modules attached after hydration, or client-side runtime injection if you need to avoid the refresh-time flash in SSR/SSG apps.
Each package ships the same rule as predefine.css, so the simplest option is to import it from your top-level entry:
// React / Next.js top-level entry
import '@accretion_ui/react/predefine.css';/* Angular 18-20 src/styles.css */
@import "@accretion_ui/angular_18/predefine.css";
/* Angular 21+ src/styles.css */
@import "@accretion_ui/angular_21/predefine.css";
/* Core-only usage */
@import "@accretion_ui/core/predefine.css";The wrappers and core runtime also inject the same stylesheet once on the client as a fallback, but that runtime injection cannot prevent a first-paint flash if the CSS was not already present in the initial HTML/CSS payload.
Accretion UI is client-first today. The components can still participate in SSR and SSG, but only with caveats:
- The server can emit the custom element tags, but the real behavior and styling arrive only after the browser defines and upgrades those elements on the client.
- React and Angular hydrate before the web components finish upgrading, so framework hydration can observe markup, attributes, or nested structure that is still in a pre-upgrade state.
- The wrappers proxy props and events onto custom elements rather than rendering native React or Angular component trees, which means strict framework-managed hydration parity is harder to guarantee.
In practice, SSR is useful here for faster first HTML delivery and progressive enhancement, but regions that require strict hydration parity should still prefer client-only rendering or a framework hydration opt-out until the web-component SSR path is more mature.
npx @angular/cli@18 new my-app
cd my-app
npm install @accretion_ui/angular_18 @accretion_ui/coresrc/app/app.component.ts
import { Component } from '@angular/core';
import { AccretionButton } from '@accretion_ui/angular_18';
@Component({
selector: 'app-root',
standalone: true,
imports: [AccretionButton],
template: `
<p>Count: {{ count }}</p>
<accretion-button variant="primary" (click)="increment()">Increment Count</accretion-button>
<accretion-button variant="secondary" (click)="decrement()">Decrement Count</accretion-button>
<accretion-button variant="tertiary" (click)="reset()">Reset Count</accretion-button>
`
})
export class AppComponent {
count = 0;
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
reset() {
this.count = 0;
}
}ng new my-app
cd my-app
npm install @accretion_ui/angular_21 @accretion_ui/coresrc/app/app.ts
import { Component, signal } from '@angular/core';
import { AccretionButton } from '@accretion_ui/angular_21';
@Component({
selector: 'app-root',
standalone: true,
imports: [AccretionButton],
template: `
<p>Count: {{ count() }}</p>
<accretion-button variant="primary" (click)="increment()">Increment Count</accretion-button>
<accretion-button variant="secondary" (click)="decrement()">Decrement Count</accretion-button>
<accretion-button variant="tertiary" (click)="reset()">Reset Count</accretion-button>
`
})
export class App {
count = signal(0);
increment() {
this.count.update((value) => value + 1);
}
decrement() {
this.count.update((value) => value - 1);
}
reset() {
this.count.set(0);
}
}npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm install @accretion_ui/reactsrc/App.tsx
import { useState } from 'react';
import { AccretionButton } from '@accretion_ui/react';
export default function App() {
const [count, setCount] = useState(0);
return (
<>
<p>Count: {count}</p>
<AccretionButton variant="primary" onClick={() => setCount((value) => value + 1)}>Increment Count</AccretionButton>
<AccretionButton variant="secondary" onClick={() => setCount((value) => value - 1)}>Decrement Count</AccretionButton>
<AccretionButton variant="tertiary" onClick={() => setCount(0)}>Reset Count</AccretionButton>
</>
);
}npx create-react-app@latest my-app --template typescript
cd my-app
npm install @accretion_ui/reactsrc/App.tsx
import { useState } from 'react';
import { AccretionButton } from '@accretion_ui/react';
function App() {
const [count, setCount] = useState(0);
return (
<>
<p>Count: {count}</p>
<AccretionButton variant="primary" onClick={() => setCount((value) => value + 1)}>Increment Count</AccretionButton>
<AccretionButton variant="secondary" onClick={() => setCount((value) => value - 1)}>Decrement Count</AccretionButton>
<AccretionButton variant="tertiary" onClick={() => setCount(0)}>Reset Count</AccretionButton>
</>
);
}
export default App;npx create-next-app@latest my-app --typescript
cd my-app
npm install @accretion_ui/reactapp/page.tsx
'use client';
import { useState } from 'react';
import { AccretionButton } from '@accretion_ui/react';
export default function Page() {
const [count, setCount] = useState(0);
return (
<>
<p>Count: {count}</p>
<AccretionButton variant="primary" onClick={() => setCount((value) => value + 1)}>Increment Count</AccretionButton>
<AccretionButton variant="secondary" onClick={() => setCount((value) => value - 1)}>Decrement Count</AccretionButton>
<AccretionButton variant="tertiary" onClick={() => setCount(0)}>Reset Count</AccretionButton>
</>
);
}Accretion UI uses multiple independent test layers so releases are not validated by a single signal:
- Story-level behavior tests confirm stories behave the way they claim.
- Accessibility scans catch WCAG-impacting markup/semantics issues.
- Keyboard and zoom-focused checks validate interaction quality beyond static rules.
- Smoke tests validate real app installs in React and Angular before and after publish.
| Test layer | Command | Primary purpose | Typical failure signal |
|---|---|---|---|
| Core package test runner | npm --prefix components/core run test |
Runs Stencil spec/e2e suite (currently no committed spec files; command kept CI-safe). | Build/test harness failure in components/core. |
| Storybook behavior + prop coverage + Playwright accessibility | npm --prefix chromatic run test:accordion |
Validates Accordion stories, prop behavior, and framework parity across React, Angular 18, Angular 21. | Story behavior regression, prop contract break, keyboard/interaction bug. |
Storybook accessibility addon (@storybook/addon-a11y) |
Included in each Storybook preview/build | Provides per-story a11y scans in Storybook UI and Chromatic context. | WCAG rule violations flagged in Storybook addon panel. |
| USWDS-aligned keyboard/zoom checks (Playwright) | Included in npm --prefix chromatic run test:accordion |
Covers keyboard interaction and zoom behavior that static axe scans do not fully cover. | Focus order, keyboard toggle, zoom overflow/clipping regressions. |
| Local package smoke matrix | npm --prefix testing run verify:local |
Verifies local tarballs install/build in React Vite, React CRA, React Next, Angular 18, Angular 21. | Consumer import/build failures before publish. |
| npm package smoke matrix | npm --prefix testing run verify:npm |
Verifies published npm artifacts install/build in the same framework matrix. | Published package regression, bad manifest/exports/dependency issues. |
| npm runtime browser smoke matrix | npm --prefix testing run verify:npm:browser |
Starts each npm smoke app in a real browser and validates button + accordion interactions with Playwright. | Runtime hydration/interaction failures not caught by build-only smoke checks. |
addon-a11yand@axe-core/playwrightboth run axe rules, but in different execution contexts.- Axe does not fully cover keyboard workflow expectations (for example, open/close behavior on
Enter/Space, tab sequencing through panel content), so dedicated keyboard tests remain required. - Accordion-specific keyboard/zoom tests in
chromatic/tests/specs/accordion.uswds-a11y.spec.tscover USWDS-style interaction expectations that are not purely rule-based.
From repo root:
npm --prefix components/core run test
npm --prefix chromatic run test:accordion
npm --prefix testing run verify:localAfter publish, run:
npm --prefix testing run verify:npm
npm --prefix testing run verify:npm:browser- Add or update Storybook stories first in each framework workspace (
chromatic/react,chromatic/angular_18,chromatic/angular_21). - Add story IDs to
chromatic/tests/specs/helpers.tsand keep IDs framework-agnostic (for example,accretion-accordion--*). - Add behavior and prop assertions in
accordion.behavior.spec.tsand/oraccordion.props.spec.ts. - Add accessibility expectations in:
accordion.a11y.spec.tsfor axe blocking violations.accordion.uswds-a11y.spec.tsfor keyboard/zoom/interaction expectations.
- Re-run
npm --prefix chromatic run test:accordionand both smoke scripts before publish.
The testing/ workspace is the smoke-test harness that replaced committed demo apps (apps/ and live_apps/).
This keeps the repository slim while still verifying real install paths.
# 1) Verify local package changes before publish
npm --prefix testing run verify:local
# 2) Verify npm-published packages after publish
npm --prefix testing run verify:npm
# 3) Verify npm-published runtime behavior in a real browser
npm --prefix testing run verify:npm:browser- Builds local packages:
core,react,angular_18,angular_21. - Packs each into tarballs.
- Generates temporary apps under
.tmp/smoke-local:- React Vite
- React CRA
- React Next.js
- Angular 18
- Angular 21
- Installs local tarballs into those apps.
- Runs framework builds to confirm imports and compilation.
- Generates temporary apps under
.tmp/smoke-npmfor the same framework matrix. - Installs from npm registry instead of local tarballs.
- Runs framework builds to validate published artifacts.
- If a published wrapper version does not yet expose Accordion exports, the harness falls back to button-only smoke checks and logs that fallback explicitly.
- Runs
verify:npmfirst to regenerate npm smoke apps. - Starts each generated app server sequentially.
- Runs Playwright Chromium checks against each framework target:
- React Vite
- React CRA
- React Next.js
- Angular 18
- Angular 21
- Validates runtime behavior in browser (button interactions + Accordion interaction where exports exist).
Version pinning is supported:
ACCRETION_CORE_VERSION=<core_version> \
ACCRETION_REACT_VERSION=<react_version> \
ACCRETION_ANGULAR_18_VERSION=<angular_18_version> \
ACCRETION_ANGULAR_21_VERSION=<angular_21_version> \
npm --prefix testing run verify:npmFor Accordion release validation, always pin the just-published package versions so the smoke run validates Accordion imports directly (instead of fallback mode).
npm --prefix .tmp/smoke-local/react-vite-local start
npm --prefix .tmp/smoke-local/react-cra-local start
npm --prefix .tmp/smoke-local/react-next-local dev
npm --prefix .tmp/smoke-local/angular-18-local start
npm --prefix .tmp/smoke-local/angular-21-local startValidate:
- The three variants render with distinct visual intent.
- Slot text appears inside the button.
- Increment/decrement/reset actions update framework state.
- Accordion imports render in React and Angular smoke apps without compile/runtime errors.
- Accordion trigger/panel interactions render expected expanded/collapsed states.
Chromatic workspace folders:
chromatic/reactchromatic/angular_18chromatic/angular_21
Install dependencies once:
cd chromatic
npm run install:allRun Storybook locally:
npm run storybook:react
npm run storybook:angular_18
npm run storybook:angular_21Publish to Chromatic:
npm run chromatic:react
npm run chromatic:angular_18
npm run chromatic:angular_21Required environment variables (~/.config/accretion_ui/chromatic.env):
export CHROMATIC_PROJECT_TOKEN_REACT="<token>"
export CHROMATIC_PROJECT_TOKEN_ANGULAR_18="<token>"
export CHROMATIC_PROJECT_TOKEN_ANGULAR_21="<token>"Use this checklist before merging a release branch and publishing packages:
- Scope is confirmed for all framework wrappers (
core,react,angular_18,angular_21). - Storybook stories are updated for all affected frameworks.
-
npm --prefix components/core run testpasses. -
npm --prefix chromatic run test:accordionpasses. -
npm --prefix testing run verify:localpasses. - Package versions are bumped and wrapper core ranges are aligned.
- Production builds for all publishable packages pass.
- Chromatic publishes are complete and README Storybook links point to the latest builds.
- npm authentication is confirmed (
npm whoami). - Publish commands run in strict dependency order (
corefirst). -
npm --prefix testing run verify:npmpasses using published versions. -
npm --prefix testing run verify:npm:browserpasses against published versions.
Use this section for design leads, product, and engineering leads before commands are run.
- Confirm release scope:
- Which components changed, what behavior changed, and which frameworks are impacted.
- Confirm visual sign-off:
- Review Storybook/Chromatic for React, Angular 18-20, and Angular 21+.
- Confirm compatibility sign-off:
- React track (
@accretion_ui/react) and Angular tracks (@accretion_ui/angular_18,@accretion_ui/angular_21) still match the support matrix.
- React track (
- Confirm rollout communication:
- Decide release notes and internal/external announcement timing.
- Confirm go/no-go:
- Release only after smoke checks pass and npm publish credentials are available.
Run all commands from repo root unless noted otherwise.
# Confirm npm auth (required for publish)
npm whoami
# Optional: verify clean working tree before release
git status --short# Core package test runner
npm --prefix components/core run test
# Story-level behavior + prop + accessibility coverage
npm --prefix chromatic run test:accordion
# Full local artifact + consumer smoke matrix (React Vite/CRA/Next + Angular 18/21)
npm --prefix testing run verify:localChoose patch, minor, or major based on the release scope.
npm --prefix components/core version patch --no-git-tag-version
npm --prefix components/react version patch --no-git-tag-version
npm --prefix components/angular_18 version patch --no-git-tag-version
npm --prefix components/angular_21 version patch --no-git-tag-versionAfter bumping core, align wrapper references to the new core version:
CORE_VERSION="$(node -p "require('./components/core/package.json').version")"
npm --prefix components/react pkg set "dependencies.@accretion_ui/core=^${CORE_VERSION}"
npm --prefix components/angular_18 pkg set "peerDependencies.@accretion_ui/core=^${CORE_VERSION}"
npm --prefix components/angular_21 pkg set "peerDependencies.@accretion_ui/core=^${CORE_VERSION}"
npm --prefix components/react install --package-lock-only
npm --prefix components/angular_18 install --package-lock-only
npm --prefix components/angular_21 install --package-lock-onlynpm --prefix components/core run build
npm --prefix components/react run build
npm --prefix components/angular_18 run build
npm --prefix components/angular_21 run buildnpm --prefix components/core run publish:package
npm --prefix components/react run publish:package
npm --prefix components/angular_18 run publish:package
npm --prefix components/angular_21 run publish:packageCORE_VERSION="$(node -p "require('./components/core/package.json').version")"
REACT_VERSION="$(node -p "require('./components/react/package.json').version")"
ANGULAR_18_VERSION="$(node -p "require('./components/angular_18/package.json').version")"
ANGULAR_21_VERSION="$(node -p "require('./components/angular_21/package.json').version")"
npm view @accretion_ui/core version
npm view @accretion_ui/react version
npm view @accretion_ui/angular_18 version
npm view @accretion_ui/angular_21 version
ACCRETION_CORE_VERSION="$CORE_VERSION" \
ACCRETION_REACT_VERSION="$REACT_VERSION" \
ACCRETION_ANGULAR_18_VERSION="$ANGULAR_18_VERSION" \
ACCRETION_ANGULAR_21_VERSION="$ANGULAR_21_VERSION" \
npm --prefix testing run verify:npm
ACCRETION_CORE_VERSION="$CORE_VERSION" \
ACCRETION_REACT_VERSION="$REACT_VERSION" \
ACCRETION_ANGULAR_18_VERSION="$ANGULAR_18_VERSION" \
ACCRETION_ANGULAR_21_VERSION="$ANGULAR_21_VERSION" \
npm --prefix testing run verify:npm:browserRelease guardrails:
- Publish
@accretion_ui/corefirst. - Publish wrapper packages only after core publish succeeds.
- Never republish an existing version number.
- If publish fails mid-sequence, fix the issue and resume from the first unpublished package.
Run from repo root in this exact order:
npm --prefix components/core run publish:package
npm --prefix components/react run publish:package
npm --prefix components/angular_18 run publish:package
npm --prefix components/angular_21 run publish:packageRecommended post-publish verification:
CORE_VERSION="$(node -p "require('./components/core/package.json').version")"
REACT_VERSION="$(node -p "require('./components/react/package.json').version")"
ANGULAR_18_VERSION="$(node -p "require('./components/angular_18/package.json').version")"
ANGULAR_21_VERSION="$(node -p "require('./components/angular_21/package.json').version")"
ACCRETION_CORE_VERSION="$CORE_VERSION" \
ACCRETION_REACT_VERSION="$REACT_VERSION" \
ACCRETION_ANGULAR_18_VERSION="$ANGULAR_18_VERSION" \
ACCRETION_ANGULAR_21_VERSION="$ANGULAR_21_VERSION" \
npm --prefix testing run verify:npm
ACCRETION_CORE_VERSION="$CORE_VERSION" \
ACCRETION_REACT_VERSION="$REACT_VERSION" \
ACCRETION_ANGULAR_18_VERSION="$ANGULAR_18_VERSION" \
ACCRETION_ANGULAR_21_VERSION="$ANGULAR_21_VERSION" \
npm --prefix testing run verify:npm:browser- Server-rendered application support still has caveats:
- Import the package
predefine.cssfile globally in SSR/SSG apps to avoid a first-paint flash while custom elements are still unresolved. - The runtime now injects the same stylesheet once per document as a fallback, but runtime injection alone cannot eliminate flash before the first HTML/CSS paint.
- The library is still client-first because behavior is finalized during client-side custom-element upgrade, not during server render.
- In SSR + hydration workflows (for example, Next.js App Router and Angular Universal), web-component upgrade timing can still produce hydration warnings or behavior differences for nested components.
- If strict SSR hydration parity is required, use framework-level client-only rendering or hydration opt-out for affected component regions until full SSR compatibility is formally released.
- Import the package
- Branch from
main. - Use
codex/<feature_name>naming. - Keep change sets scoped (core logic, wrappers, stories, docs, testing).
- Update component logic/styles in
components/core. - Build wrappers (
components/react,components/angular_18,components/angular_21). - Run smoke tests in
testing/(verify:local, thenverify:npm+verify:npm:browserafter publish). - Update affected stories in
chromatic/*. - Publish Chromatic and verify links.
- Update README if behavior, support ranges, or workflows changed.
main: stable integration branch.codex/primitive_tokens: primitive token definitions and naming.codex/component_library: initial multi-wrapper package architecture.codex/storybook_chromatic: Storybook and Chromatic integration.codex/button_semantic_tokens: semantic button token work and variant behavior updates.
- Core development context: accretion_ui
- React package repo: accretion_react
- Angular 18 package repo: accretion_angular_18
- Angular 21 package repo: accretion_angular_21
- React Chromatic repo: accretion_ui_react_chromatic
- Angular 18 Chromatic repo: accretion_ui_angular_18_chromatic
- Angular 21 Chromatic repo: accretion_ui_angular_21_chromatic
components/core: Stencil core (@accretion_ui/core)components/react: React wrapper (@accretion_ui/react)components/angular_18: Angular 18-20 wrapper (@accretion_ui/angular_18)components/angular_21: Angular 21 wrapper (@accretion_ui/angular_21)chromatic: Storybook + Chromatic projects for all wrapperstesting: generated smoke-test harness for local tarballs and npm-installed packages.tmp: temporary generated apps used by smoke testing (gitignored)
- Add per-component Figma references with direct token mapping links.
- Move proof-of-concept Button styles to finalized Figma-backed token definitions.
- Expand component coverage beyond Button while preserving one-core/multi-wrapper generation.
- Add documented accessibility expectations and test status per component.