Skip to content

[react@0.9.1] Published bundle ships empty CSS-module class refs — Button / TextField / ChoicePicker render unstyled #1307

@surfai

Description

@surfai

Symptom

Components emitted by @a2ui/react/v0_9 render with no padding, border, color, or hover state when used via the published npm tarball (@a2ui/react@0.9.1, sha ea8e3a3c7d3dca12921cb230ef06144d95454098). Three components reproduce:

  • <Button><button class="undefined undefined">label</button>
  • <TextField><div class="undefined"><input class="undefined" /></div>
  • <ChoicePicker> → similar empty class refs

<Card>, <Column>, <Row>, <Text>, <Icon>, <Image>, <Divider>, <CheckBox>, <Slider> are unaffected — they use inline-style objects in source (no .module.css import) or rely on the .a2uiText / .a2uiCaption plain class names that DO ship in v0_9/index.css.

Reproduction (minimal, deterministic)

mkdir /tmp/a2ui-repro && cd /tmp/a2ui-repro
npm init -y
npm install @a2ui/react@0.9.1
grep -A6 'var Button_default' node_modules/@a2ui/react/v0_9/index.js | head -8

Output (verified 2026-04-30 against the published tarball):

// src/v0_9/catalog/basic/components/Button.module.css
var Button_default = {};
var Button = createComponentImplementation(ButtonApi, ({ props, buildChild }) => {
  useBasicCatalogStyles();
  const classes = [Button_default.button];
  if (props.variant === "primary") {
    classes.push(Button_default.primary);

Button_default is the JS-side object that should hold the CSS-module-hashed class names from Button.module.css (e.g. {button: "Button-button-abc123", primary: "Button-primary-def456"}). It's empty in the published tarball, so classes.join(" ") produces the literal string "undefined undefined". Same applies to TextField_default and ChoicePicker_default (grep them in the same file to confirm).

The published package.json exports "./styles/structural.css": "./structural.css", but find node_modules/@a2ui/react -name structural.css returns nothing — the file isn't actually shipped in the tarball. node_modules/@a2ui/react/v0_9/index.css exists but contains only .a2uiText / .a2uiCaption rules (no .a2uiButton, .a2uiPrimary, .a2uiBorderless, etc.).

Why the official samples/client/react/shell/ works (and users see broken output)

samples/client/react/shell/package.json declares:

"@a2ui/react": "file:../../../../renderers/react"

This is a file: dep pointing at local source, so Vite handles .module.css natively at build time and class names get populated correctly. The shell sample never goes through the npm publish/install path, so its visual parity tests don't catch this regression. Users running npm install @a2ui/react get the broken bundle.

The shell's App.css ships zero [data-a2ui-surface] button rules — confirms the renderer is supposed to be self-styled by its CSS modules. There's no documented "you must also import @a2ui/react/styles/..." step that would compensate.

Source vs published bundle (root cause)

Source: renderers/react/src/v0_9/catalog/basic/components/Button.tsx

import styles from './Button.module.css';

export const Button = createComponentImplementation(ButtonApi, ({props, buildChild}) => {
  useBasicCatalogStyles();
  const classes = [styles.button];
  if (props.variant === 'primary') classes.push(styles.primary);
  // ...
});

The source import styles from './Button.module.css' is preserved as var Button_default in the bundle, but the build pipeline (tsup per renderers/react/package.json scripts.build) is publishing it as {} — the module's exports aren't being captured. CheckBox/Slider work because their source uses const containerStyle = {...} inline objects rather than CSS-module imports.

Related issues

Workaround for users

Two options:

  1. Use file: dep like the official shell sample (requires git clone https://github.com/google/A2UI.git + building locally on each consumer machine):

    "@a2ui/react": "file:../path/to/A2UI/renderers/react"
  2. Element-selector CSS shim scoped to the surface wrapper. Doesn't reproduce SDK branding exactly but restores baseline visual feedback:

    [data-a2ui-surface] button { padding: 0.5rem 1rem; border: 1px solid var(--border); border-radius: 0.5rem; ... }
    [data-a2ui-surface] input,
    [data-a2ui-surface] textarea { padding: 0.5rem 0.75rem; border: 1px solid var(--border); ... }

Environment

  • @a2ui/react@0.9.1 (latest published; 0.9.0, 0.9.0-alpha.3/4 show same shape per npm view)
  • @a2ui/web_core@0.9.2
  • @a2ui/markdown-it@0.0.3
  • Next.js 16 + React 19 + Tailwind v4 host application
  • Node 20.20.2 / npm 10

This report was prepared with AI assistance (Claude Code). Repro commands and source/bundle excerpts above were executed against the public tarball and the v0.9 source on main.

Metadata

Metadata

Assignees

Labels

type: bugSomething isn't working

Type

No type

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions