README.md

August 2, 2026 · View on GitHub

Angular Starter UI — ready-to-use Angular design system starter

CI Angular Storybook License: MIT pnpm

Ready-to-use Angular starter for building a design system: a publishable component library (standalone, zoneless, signals) and a Storybook workshop, with every best practice and tool already wired up.

The starter ships a documented set of design tokens (neutral zinc scale, light and dark via light-dark()) and three example components (button, input, badge) showing the conventions: signal inputs, host class bindings, Tailwind utilities consuming the tokens, stories with interaction tests, and 100% unit test coverage.

Stack

ToolRole
Angular 22 + ng-packagrFramework and library build (Angular Package Format)
Storybook 10 (@storybook/angular-vite)Component workshop: autodocs, a11y addon, theme switcher
Tailwind CSSUtility-first CSS consuming the design tokens
ESLint + angular-eslint + eslint-plugin-storybookLint for TypeScript code, templates and stories
Prettier + sort-imports + tailwindcssCode formatting, import ordering and class sorting
Vitest + Testing LibraryUnit tests (Angular's default runner)
Storybook test-runnerRuns every story and its play interaction tests in CI
SheriffEnforces module boundaries (components stay isolated)
Husky + lint-stagedGit hooks (format + lint on commit)
commitlintCommit message validation (Conventional Commits)
GitHub ActionsCI: format, lint, unit tests, builds, interaction tests

Getting started

pnpm install    # installs dependencies
pnpm start      # Storybook on http://localhost:6006

Storybook is the development surface: components are built and reviewed through their stories, there is no demo application.

Scripts

ScriptDescription
pnpm startStorybook dev server on http://localhost:6006
pnpm run buildBuilds the library into dist/ui (Angular Package Format)
pnpm run build:devDevelopment build of the library
pnpm run build-storybookBuilds the static Storybook into storybook-static/
pnpm testUnit tests (Vitest)
pnpm run test:coverageUnit tests with coverage report and thresholds
pnpm run test-storybookInteraction tests against a running Storybook (pnpm start first)
pnpm run test-storybook:ciServes storybook-static/ and runs the interaction tests on it
pnpm run lintLint (ESLint)
pnpm run lint:fixLint with automatic fixes
pnpm run formatFormat the whole project (Prettier)
pnpm run format:checkCheck formatting without modifying anything

Project structure

.storybook/                  # Storybook config: main.ts, preview.ts (theme switcher), preview.css
projects/ui/
├── ng-package.json          # ng-packagr config (entry point, shipped assets)
├── package.json             # Library manifest (rename before publishing)
├── styles/
│   └── tokens.css           # Design tokens — shipped as dist/ui/styles/tokens.css
├── docs/                    # Storybook "Foundations" MDX pages (colors, typography)
└── src/
    ├── public-api.ts        # The library's only entry point
    └── lib/
        ├── button/          # button.ts + button.spec.ts + button.stories.ts
        ├── input/
        └── badge/

Each component lives in its own folder with its spec and stories co-located. Dependency rules, enforced at lint time by Sheriff (sheriff.config.ts): components cannot import each other — shared building blocks get their own module. Modules are barrel-less: import files directly (no index.ts), and place files a module wants to keep private in an internal/ subdirectory. public-api.ts is the single public entry point of the package.

Design tokens and theming

All colors come from projects/ui/styles/tokens.css, a pure token sheet (no resets, no element styles) shipped with the package. Tokens follow the shadcn/ui semantic convention on a neutral zinc scale.

Each token declares its light and dark value with light-dark(); the active scheme follows the OS preference unless the host forces one:

<html data-theme="dark">
  <!-- forces dark, whatever the OS says -->
</html>

The Storybook toolbar theme switcher does exactly that, so every story can be reviewed in both themes. Components consume tokens through Tailwind arbitrary-value utilities (bg-(--primary), border-(--border), ...): retheming means editing one CSS file. The tokens are documented in Storybook under Foundations.

Components

Three example components demonstrate the conventions (prefix ui, signal inputs, host class bindings):

ComponentSelectorAPI
Buttonbutton[ui-button]variant: primary | secondary | destructive | ghost — size: sm | md | lg
Inputinput[uiInput]Styled native input: works with any forms API without plumbing
Badgeui-badgevariant: primary | secondary | destructive | outline

Button and input attach to native elements (Material-style attribute selectors), keeping native semantics, accessibility and forms compatibility for free:

<button ui-button variant="destructive" size="sm">Delete</button>
<input uiInput type="email" placeholder="you@example.com" />
<ui-badge variant="outline">Draft</ui-badge>

Storybook

  • Autodocs: every story is tagged autodocs globally (.storybook/preview.ts); each component gets a generated docs page from its stories and argTypes.
  • Accessibility: the a11y addon runs axe checks on every story.
  • Theming: the toolbar switcher toggles data-theme on <html> via withThemeByDataAttribute, matching the tokens' light-dark() contract.
  • Interaction tests: stories declare play functions (imports from storybook/test); the test-runner executes every story in Chromium in CI (pnpm run test-storybook:ci).
  • Foundations: MDX pages in projects/ui/docs document the color tokens (light and dark swatches) and typography.

Using the library in your app

The publishable artifact is built from projects/ui into dist/ui. Before publishing, rename the package in projects/ui/package.json (e.g. @your-scope/ui) — the root package.json version is the starter's own version (managed by release-please), the library version is managed in projects/ui/package.json.

In the consuming app:

  1. Install the package and its peer dependencies (@angular/common, @angular/core).
  2. Import the token sheet once, e.g. in styles.css:
    @import '@your-scope/ui/styles/tokens.css';
    
  3. Tailwind consumers: the library's templates are inside node_modules, which Tailwind does not scan by default. Add a @source so the utilities used by the components are generated:
    @import 'tailwindcss';
    @source '../node_modules/@your-scope/ui';
    
    Non-Tailwind consumers only need the tokens import: component class names would then need to be covered by your own CSS strategy — in that case prefer forking the components into your design system, which is what a starter is for.

The library exports each component's own types so you never recreate them. Every variant/size union comes with a matching as const tuple (the single source of truth the type is derived from), so you get both the type and the runtime list:

import { BUTTON_VARIANTS, type ButtonVariant } from '@your-scope/ui';

// Type your own props with the library's union...
function setTone(variant: ButtonVariant) {
  /* ... */
}

// ...and iterate the values without hardcoding them (selects, validation, docs).
const options = BUTTON_VARIANTS.map((variant) => ({ label: variant, value: variant }));

Exported today: ButtonVariant / BUTTON_VARIANTS, ButtonSize / BUTTON_SIZES, BadgeVariant / BADGE_VARIANTS.

Testing

  • Unit tests use Angular Testing Library (render, screen, userEvent): querying by role or label asserts accessibility for free. The jest-dom matchers are registered in projects/ui/src/test-setup.ts.
  • Coverage is at 100% and must stay there; the CI thresholds (85/80/70/85 in angular.json) are intentionally lower so downstream users of the starter are not blocked. Stories are excluded from coverage.
  • Interaction tests live in the stories themselves (play functions) and run in a real browser via the Storybook test-runner — the Vitest addon is not used because it does not support Angular.

Quality and conventions

  • On commit: lint-staged formats and lints the staged files; commitlint enforces Conventional Commits (feat: ..., fix: ..., ...).
  • In CI (.github/workflows/ci.yml): format check, lint, unit tests, library build, Storybook build and interaction tests on every push/PR.
  • Code conventions: see the Angular style guide. In short: standalone components (OnPush change detection is the Angular default since v22, no need to declare it), signals (input(), computed()), inject(), private # properties, control flow (@if, @for), no .component/.directive suffix in file names.
  • Config files use ESM .mjs where the tool supports it: eslint.config.mjs, prettier.config.mjs, commitlint.config.mjs.

Contributing

See CONTRIBUTING.md for the contribution workflow and CODE_OF_CONDUCT.md for community guidelines. To report a vulnerability, see SECURITY.md.

License

This project is licensed under MIT.