Skip to content

Adding a component

The shortest path from “I want a new component” to “merged and documented”:

1. Scaffold under packages/core/src/components/<name>/

packages/core/src/components/banner/
Banner.tsx # the component
banner.css # styles (CSS variables, no hard-coded values)
spec.md # the contract (see below)

Follow naming conventions:

  • Component file: PascalCase (Banner.tsx)
  • Directory: kebab-case (banner/)
  • CSS file: kebab-case (banner.css)
  • Spec file: always spec.md

2. Write the component

Two rules that keep helixui coherent:

  • CSS variables, not inline values. Every color, spacing, radius, font size — all of them come from --helixui-* custom properties.
  • No global state. Components are pure; if you need state, lift it.
import './banner.css';
import type { ReactNode } from 'react';
export interface BannerProps {
tone?: 'info' | 'success' | 'warning' | 'danger';
icon?: ReactNode;
children: ReactNode;
}
export function Banner({ tone = 'info', icon, children }: BannerProps) {
return (
<div className={`helixui-banner helixui-banner--${tone}`} role="status">
{icon ? <span className="helixui-banner__icon">{icon}</span> : null}
<div className="helixui-banner__body">{children}</div>
</div>
);
}

3. Write the spec

spec.md is read by three different consumers: the docs site, the LLM manifest, and the MCP server. Keep it accurate.

---
title: Banner
import: "import { Banner } from '@helixui/core'"
description: "A horizontal callout for inline messages."
props:
- { name: tone, type: '"info" | "success" | "warning" | "danger"', default: '"info"' }
- { name: icon, type: 'ReactNode', default: 'undefined' }
tokens:
- color.bg.banner.info
- color.text.banner.info
- radius.md
- spacing.3
a11y:
- role="status" on the root
- icon should be aria-hidden (decorative)
---
## Anatomy
A `<div role="status">` with an optional icon slot and a body slot.
## Examples
```tsx
<Banner tone="success">Saved.</Banner>
<Banner tone="warning" icon={<Warning />}>Rate limit approaching.</Banner>
## 4. Export it
Add the re-export to `packages/core/src/index.ts` in alphabetical order:
```ts
export { Banner, type BannerProps } from './components/banner/Banner.js';

5. Build a showcase (if it warrants one)

For components that show their value in context (forms, layouts, data displays), add a usage in apps/site/src/pages/showcase/. For atomic primitives (Text, Icon, etc.) the docs page from spec.md is enough.

Verify

Terminal window
pnpm typecheck # types are valid
pnpm build:llms # spec.md is picked up
pnpm dev:docs # spec renders correctly on the docs site

If build:llms fails complaining about a missing spec, you forgot step 3.