# helia-ui

`@ambiqai/helia-ui` is the shared design system behind the HELIA developer
surfaces: the tokens, the Astro parts, the shadcn React components, and the
Starlight theme. It ships as source with no build step, so a consuming site's
bundler reads the stylesheets and components directly.

This site is one of its consumers. Every page here reaches the package through
the export map and the Starlight plugin, never by relative path, so what you
read on a page is what an unrelated site gets.

## Install

```bash
npm install @ambiqai/helia-ui
```

Because the package ships source rather than a bundle, the consuming site
supplies the toolchain. These are peer dependencies and the site needs the same
majors:

| Peer                 | Range     |
| -------------------- | --------- |
| `astro`              | `^7.0.2`  |
| `@astrojs/starlight` | `^0.41.5` |
| `react`              | `^19`     |
| `react-dom`          | `^19`     |
| `tailwindcss`        | `^4.3.3`  |

A site wires the package in three places: the Starlight plugin in
`astro.config.mjs`, its own Tailwind entry, and the React integration if it
uses the React lane.

```js
// astro.config.mjs
import { heliaStarlight } from '@ambiqai/helia-ui/starlight';

export default defineConfig({
  integrations: [
    starlight({
      customCss: ['./src/styles/tailwind.css'],
      plugins: [heliaStarlight({ footer: { links, tagline } })],
    }),
    react(),
  ],
  vite: { plugins: [tailwindcss()] },
});
```

```css
/* src/styles/tailwind.css */
@import '@ambiqai/helia-ui/starlight-tailwind.css';

@source '../content/**/*.{md,mdx}';
@source '../components/**/*.{astro,tsx}';
@source '../../node_modules/@ambiqai/helia-ui/astro/**/*.astro';
```

Tailwind resolves a `@source` glob relative to the file that declares it, so
the package cannot express a path into your tree. Every site therefore keeps a
Tailwind entry that imports the package's and names its own sources, including
the package directories whose parts it renders. A `@source` that reaches back
into `node_modules` is fine — an explicit `@source` is not subject to the
ignore rules automatic detection applies — so `@source
  '../../node_modules/@ambiqai/helia-ui/astro/**/*.astro'` is the working form.

## The export map

| Export                     | What it is                                                                                              |
| -------------------------- | ------------------------------------------------------------------------------------------------------- |
| `./tokens.css`             | Primitives for both themes, and the mapping onto Starlight's `--sl-*` variables                         |
| `./semantic.css`           | Spacing, radius, type, weight, leading, ink, surfaces, tones                                            |
| `./recipes.css`            | `helia-surface`, `chip`, `eyebrow`, `button`, `card`, `badge`, `media`, and the focus and hover recipes |
| `./starlight.css`          | Unlayered chrome overrides: sidebar, header, search, table of contents, steps, skinned built-ins        |
| `./tailwind.css`           | Layer order and the `@theme` mapping, with no Starlight dependency                                      |
| `./starlight-tailwind.css` | `./tailwind.css` with `@astrojs/starlight-tailwind` in front; what a Starlight site imports             |
| `./shadcn.css`             | The shadcn variable bridge, imported by `./tailwind.css`                                                |
| `./astro/*`                | The Astro parts, one file per part                                                                      |
| `./react/*`                | The generated shadcn components, one file per component                                                 |
| `./starlight`              | The `heliaStarlight()` plugin                                                                           |
| `./starlight/*`            | The shell components the plugin installs                                                                |

The four stylesheets the plugin injects are unlayered on purpose. Unlayered
rules beat every cascade layer, so the token sheets stay the source of truth no
matter what the Starlight Tailwind compat package wires up below them. See
[the Starlight plugin](https://ambiqai.github.io/helia-ui/starlight-plugin/) for the injection order.

## Three lanes

The package offers three ways to put something on a page, and the choice is not
a matter of taste.

**Astro parts** (`./astro/*`) are the default. Surface, the card family, Badge,
Chip, Button, Media, the carousel: static presentation with zero JavaScript, no
React at build time, inline in MDX. Reach for these first.

**React components** (`./react/*`) are the generated shadcn layer, for behavior
a browser cannot do alone: focus trapping, roving focus, filtered lists,
popovers that flip, sorting and paging, charts. Do not re-base an Astro part on
shadcn to get a class name.

**Stylesheets** (`./tokens.css` and the rest) carry the vocabulary both lanes
compile against, plus the Tailwind `@theme` mapping so utilities and parts use
one spacing and color scale. `@theme inline` keeps a generated utility
referencing the token directly rather than a value snapshotted at `:root`,
which is what lets utilities follow the theme toggle.

## Templates

Two starters ship in the repository rather than in the published tarball, so a
new surface begins wired up rather than assembled by hand.

[**Docs sites**](https://ambiqai.github.io/helia-ui/templates/docs-sites/) (`templates/product-docs`) is
a Starlight site with the plugin, the Tailwind entry and KaTeX already in place.

[**Web apps**](https://ambiqai.github.io/helia-ui/templates/web-apps/) (`templates/web-app`) is a Vite,
React and TypeScript application for live hardware and streaming data: a worker
that produces samples, a ring buffer that publishes one frame at a time, a
Recharts trace, and the `Source` interface a device driver implements. Docs and
marketing pages ship no JavaScript; an app's bar is responsiveness under
streaming instead.

## The component contract

Every part in `./astro/*` holds to four rules. They are what makes the package
publishable rather than one site's own components.

1. **Composition over configuration.** More than about six props means a
   composition wearing a primitive's name. Regions are children or named slots,
   never a `title` / `description` / `action` prop bundle.
2. **Data enters through props and children only.** No part imports data, and
   no part knows what a product, a demo, or a status is.
3. **Actions are handed in as children.** A card never decides where it links.
4. **No domain vocabularies.** Status names, brand marks, and product accents
   are the consumer's. The package exposes `Badge tone` and `--accent`, and the
   consumer decides what "Released" looks like.

Where a part takes a spacing prop it names a step (`'4'`, `'none'`) rather than
a length, so the value resolves through the same scale as every utility.

## The island rule

React context does not cross Astro's MDX component boundary. Astro gives each
component in an `.mdx` file its own React root and passes children through as
serialized HTML, so a compound component assembled in a page throws at build
with `` `TabsTrigger` must be used within `Tabs` ``. That covers Tabs, Dialog,
Sheet, Select, Combobox, Accordion, Command, Popover, DropdownMenu, and most of
the library.

No `.mdx` or `.astro` file imports a component from
`@ambiqai/helia-ui/react/`, by package specifier or by path. Every interactive
use is an island: one `.tsx` file that composes the pieces and exports a
single component, and the page imports it. The pages under **React
components** on this site are built that way, and the repository lint
`check:islands` enforces it.
