Back to Copilotkit

A2UI React Renderer — Style Architecture

packages/a2ui-renderer/src/react-renderer/styles/README.md

1.57.06.2 KB
Original Source

A2UI React Renderer — Style Architecture

The React renderer uses Light DOM (regular HTML elements), unlike the Lit renderer which uses Shadow DOM with built-in style encapsulation. This means all CSS lives in the global document scope and must be carefully organized to avoid conflicts with host-app stylesheets.

All styles are injected into a single <style id="a2ui-structural-styles"> element in the document head via injectStyles().

Style Layers (lowest → highest priority)

1. Browser Default Reset (reset.ts)

css
@layer a2ui-reset {
  :where(.a2ui-surface) :where(*) {
    all: revert;
  }
}

Purpose: Restores browser-default styles (heading margins, list styles, form appearance, etc.) inside .a2ui-surface. Without this, host-app CSS resets like Tailwind preflight strip defaults that the renderer expects.

Why it works: CSS @layer styles have the lowest author-level priority. Every other A2UI style is unlayered and automatically overrides the reset. The :where() wrapper also zeroes specificity for extra safety.

Why it's needed: The Lit renderer doesn't need this because Shadow DOM isolates its elements from external stylesheets. The React renderer uses Light DOM, so host-app resets leak in.

2. Structural / Utility Classes (structuralStyles)

Imported from: @a2ui/lit/0.8 → Styles.structuralStyles
Transform:     :host { ... }  →  .a2ui-surface { ... }

Utility classes shared between all renderers, generated from web_core:

PrefixSource fileExamples
layout-*styles/layout.tslayout-p-2, layout-m-0, layout-w-100
typography-*styles/type.tstypography-f-sf, typography-sz-tl
color-*styles/colors.tscolor-c-n100, color-bgc-p30
border-*styles/border.tsborder-br-12, border-bw-1
behavior-*styles/behavior.tsbehavior-ho-70
opacity-*styles/opacity.tsopacity-50

Specificity: Single class selector — (0,1,0).

These classes are applied to HTML elements via the theme's class maps (e.g. { 'layout-p-2': true, 'color-bgc-p30': true }), which components convert to className strings using classMapToString().

3. Component-Specific Styles (componentSpecificStyles)

Hand-written CSS that replicates each Lit component's static styles. Covers host-level layout (display, flex) and element-level defaults.

Two specificity tiers:

  • Host styles.a2ui-surface .a2ui-{component} — specificity (0,2,0)

    css
    .a2ui-surface .a2ui-card {
      display: block;
      flex: var(--weight);
    }
    
  • Element styles:where(.a2ui-surface .a2ui-{component}) element — specificity (0,0,1)

    css
    :where(.a2ui-surface .a2ui-image) img {
      display: block;
      width: 100%;
    }
    

    :where() zeroes the wrapper specificity so that theme utility classes (specificity (0,1,0)) can override element defaults.

4. Theme Class Maps (theme.components.*)

Applied per-component as CSS class names. The theme object provides Record<string, boolean> maps that reference utility classes from layer 2.

typescript
// From the theme object
Button: { 'color-bgc-p30': true, 'color-c-n100': true, ... }

Components merge these with classMapToString() and apply as className:

tsx
<button className={classMapToString(theme.components.Button)}>

Specificity: Same as utility classes — (0,1,0).

5. Theme Element Styles (theme.elements.*)

Applied to raw HTML elements rendered inside components (e.g. <h1>, <input>, <button> inside Text, TextField, Button). Same mechanism as component class maps.

6. Theme Additional Styles (theme.additionalStyles.*)

Applied as inline styles via React's style prop. Used for CSS custom properties and direct property overrides.

typescript
// Theme definition
additionalStyles: {
  Button: { '--n-35': 'var(--n-100)' },
  Card: { padding: '32px' },
}

// Component application
<button style={stylesToObject(theme.additionalStyles?.Button)}>

Specificity: Inline styles — (1,0,0). Always wins over class-based styles.

7. Inline Layout Styles

Components set --weight and other layout vars as inline styles based on component properties:

tsx
<div className="a2ui-card" style={{ '--weight': node.weight }}>

Specificity: (1,0,0) — same as additionalStyles.

CSS Variable Dependencies

The renderer expects color palette variables to be defined by the host application on :root or a parent element. These are consumed by the utility classes:

css
/* Neutral */     --n-0 through --n-100, --nv-0 through --nv-100
/* Primary */     --p-0 through --p-100
/* Secondary */   --s-0 through --s-100
/* Tertiary */    --t-0 through --t-100
/* Error */       --e-0 through --e-100

In the Lit renderer, @copilotkit/a2ui-renderer bundles these internally. For the React renderer, the host app must provide them (e.g. via a2ui-palette.css).

Override Priority Summary

  Highest ──┐
             │  Inline styles (additionalStyles, --weight)     (1,0,0)
             │  Theme utility classes / element classes         (0,1,0)
             │  Component host styles (.a2ui-surface .a2ui-*)  (0,2,0)
             │  Component element styles (:where(...) elem)    (0,0,1)
             │  Structural utility classes                     (0,1,0)
             │  Browser default reset (@layer a2ui-reset)      layered
  Lowest  ───┘

Note: Component host styles have higher specificity (0,2,0) than utility classes (0,1,0), but they only set structural properties (display, flex, overflow) that theme utilities don't typically target — so there is no conflict in practice.

File Overview

FilePurpose
reset.tsall: revert in @layer — restores browser defaults
index.tsStructural utilities, component CSS, injection logic