This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Umbraco.UI (UUI) is a web component library built with Lit and TypeScript. It provides 81+ reusable UI components (<uui-button>, <uui-input>, <uui-table>, etc.) published as a single npm package: @umbraco-ui/uui.
This repo has two active major versions on separate branches:
- v1 (
v1/dev) — multi-package monorepo underpackages/, Lit 2.x,@open-wc/testing. Latest: 1.17.x. PRs targetv1/dev.- v2 (
main) — single-package undersrc/, Lit 3.x, Vitest. Latest: 2.0.0-alpha. PRs targetmain.The docs below describe v1.
v1/contribno longer exists — do not use it.
# Install dependencies
npm install
# Development — starts Storybook on port 6006
npm run storybook
# Build (Vite + TypeScript declarations + custom-elements.json)
npm run build
# Clean + build
npm run clean && npm run build
# Run all component tests with coverage (browser-based)
npm run test
# Watch mode for tests
npm run test:watch
# Test a single component (use folder name, e.g. "button")
npm run test:coverage-for button
# Lint
npm run lint
# Auto-fix lint + format
npm run format
# Scaffold a new component (interactive prompts)
npm run new-componentmain(default) — v2 development branch (current: 2.x alpha), PR target for v2 work and community contributions.v1/dev— v1 maintenance branch (latest: v1.17.1)production— latest stable release, also the home for the Storybook at uui.umbraco.com (currently v1.17.1)
In v1, UUI was a monorepo of ~84 separate npm packages (@umbraco-ui/uui-button, @umbraco-ui/uui-base, etc.), each with its own package.json, Rollup config, and npm version. Build was orchestrated by Turbo + Lerna-Lite.
In v2, everything lives under a single @umbraco-ui/uui package with one Vite build. Components are individual ES modules for tree-shaking. See MIGRATION-V1-TO-V2.md for the full migration guide.
| v1 | v2 | |
|---|---|---|
| Packages | ~84 separate @umbraco-ui/uui-* |
Single @umbraco-ui/uui |
| Import | import '@umbraco-ui/uui-button'; |
import '@umbraco-ui/uui/components/button/button.js'; |
| Foundation | @umbraco-ui/uui-base/lib/... |
src/internal/... (re-exported from root) |
| CSS/Styles | @umbraco-ui/uui-css/lib/... |
src/styles/... (re-exported from root) |
| Build | Rollup per package + Turbo | Single Vite build |
| Lit version | ^2.8.0 | ^3.0.0 |
src/
├── internal/ # Foundation: mixins, events, types, registration (was uui-base)
├── styles/ # CSS custom properties, text styles (was uui-css)
├── internal/test/ # Test utilities (UUITestMouse)
├── themes/ # Light, dark, and high-contrast theme CSS
├── components/ # 80 component directories
│ ├── button/
│ │ ├── button.ts # Registration file (re-exports + defineElement call)
│ │ ├── button.element.ts # Pure component class (no side effects)
│ │ ├── button.test.ts # Tests
│ │ ├── button.story.ts # Storybook story
│ │ └── README.md
│ └── ...
└── index.ts # Root barrel — re-exports everything
stories/ # Compound/example Storybook stories (auth, scaffolding, home)
- Vite builds the library with
preserveModules(each source file → one output file) - TypeScript (
tsc -p tsconfig.build.json) generates declaration files - Lit and culori are the only runtime dependencies and are externalized (not bundled)
- Output:
dist/directory with ES modules, source maps, and.d.tsfiles - Config:
vite.config.ts— serves double duty as both build config and Vitest config
The package.json exports field exposes:
.→dist/index.js(all components)./components/*→dist/components/*(cherry-pick)./internal/*,./styles/*,./themes/*
- Single version in root
package.jsonandlerna.json(kept in sync) - Lerna-Lite handles changelog generation and npm publishing
- Conventional commits are required:
type(scope): description- Types:
feat,fix,build,docs,test,refactor,chore - Scope: component name without
uui-prefix (e.g.fix(combobox): ...) feattriggers a minor bump,fixtriggers a patch bump
- Types:
- Publishing happens from CI on
v*tags vialerna publish from-package - Pre-release versions use
preid: "rc"and publish toprereleasedist-tag - Current:
2.0.0-alpha.1onmain; v1 latest:1.17.1onv1/dev
Mixin composition — components compose behavior via mixins from internal/:
export class UUIButtonElement extends UUIFormControlMixin(
LabelMixin('', PopoverTargetMixin(LitElement))
) { ... }Element registration — each component folder has a registration file ({name}.ts) that imports the pure class and registers it. Structure: imports → side effects → types → exports:
// button/button.ts — registration file (has side effects)
import { defineElement } from '../../internal/registration/index.js';
import { UUIButtonElement } from './button.element.js';
defineElement('uui-button', UUIButtonElement);
declare global {
interface HTMLElementTagNameMap {
'uui-button': UUIButtonElement;
}
}
export * from './button.element.js';
export { UUIButtonElement as default } from './button.element.js';// button/button.element.ts — pure class (no side effects, no registration)
export class UUIButtonElement extends ... { }defineElement supports both direct call and decorator syntax. Direct calls are used in registration files; the decorator is only used for standalone example elements.
Events — custom events extend UUIEvent from internal/ for type safety.
CSS custom properties — components expose styling via --uui-* variables.
Shadow DOM — all components use shadow DOM for encapsulation.
- Element name prefixed with
uui-, class namedUUI{PascalCase}Element - Attribute reflection only for styling, not state
- No external dependencies without HQ approval
- No tag name assumptions — use
:hostorthis - JSDoc all properties, slots, events, and CSS custom properties
- Tests must pass, including basic accessibility tests
- Must have a Storybook story
- Vitest 4.x +
vitest-browser-lit+@vitest/browser-playwright, runs in Chromium locally and Chromium/Firefox/WebKit on CI - Config:
vite.config.ts(combined with build config —test:section) - Setup file:
vitest.setup.ts— registers thetoHaveNoViolationscustom matcher globally - Tests live alongside components:
src/components/{name}/{name}.test.ts - Tests must import the registration file (e.g.
import './{name}.js';) to register elements - Render elements with
renderfromvitest-browser-lit, notfixturefrom @open-wc - Accessibility testing via
axeRunfrom../../internal/test/a11y.js:import { axeRun } from '../../internal/test/a11y.js'; expect(await axeRun(element)).toHaveNoViolations();
- User interaction via
userEventfromvitest/browser
- ESLint v9 flat config (
eslint.config.js) withtypescript-eslint,eslint-plugin-lit,eslint-plugin-wc, Prettier integration - Prettier: single quotes, 2-space indent,
arrowParens: avoid,bracketSameLine: true - Pre-commit hook (Husky + lint-staged): runs ESLint, type-check on
*.element.ts, Prettier
- Node >= 24.13, npm >= 11 (see
.nvmrcandenginesin package.json) - Lit ^3.0.0
- Target: ES2022
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.