1# Web UI
2
3## Primitives
4
5Controls come from [`components/ui`](../../apps/web/src/components/ui).
6[AGENTS.md](../../AGENTS.md#taste) says how to use and extend them.
7
8Don't style a raw `<button>`, `<input>`, `<select>`, or `<textarea>` to look like a control a
9primitive provides. Raw elements are right when the element isn't that control: semantic rows, tabs,
10resize handles, swatches, editor surfaces, anything whose behavior or geometry differs by design.
11
12## Styles
13
14Style with Tailwind classes in the component that renders the element.
15
16- Global CSS, in [`index.css`](../../apps/web/src/index.css) or any other stylesheet, is only for
17 what classes on the owning component can't express: generated Markdown or imperative DOM, custom
18 elements and shadow roots, animations, runtime theme variables, and browser or Electron
19 integration.
20- No `style={{ ... }}` or inline `<style>` for static values a class covers. Values computed at
21 runtime are fine.
22- Theme-only rules use `@variant dark` and `@variant light`, not `.dark` selectors.
23
24## Composition
25
26The composer banners are the reference.
27
28- [`ComposerBanner.tsx`](../../apps/web/src/components/chat/ComposerBanner.tsx) exports one object
29 of small slots. Slots own their classes, take `className`, and spread the rest of their props,
30 merging classes with `cn`. Variants and density are props on `Root`. A slot that needs a control
31 renders `Button`.
32- [`ComposerBannerStack.tsx`](../../apps/web/src/components/chat/ComposerBannerStack.tsx) composes
33 the slots and owns only stack behavior: priority, expand and collapse, dismiss transitions.
34- Consumers are a few lines of slots, and the slots carry the styling:
35 [`ComposerActivityStatus.tsx`](../../apps/web/src/components/chat/ComposerActivityStatus.tsx).
36
37Build new UI the same way. Compose or extend a component instead of copying its markup and classes.
38When a call site repeats a treatment another one already uses, make it a variant or a slot on the
39shared component. A new composer banner or notice uses the `ComposerBanner` slots.