@cool-ai/beach-a2ui-basics
v0.1.1
Published
Generic semantic primitives for A2UI v0.9 — extends `@a2ui/web_core`'s basic catalogue with Pill, Hero, Heading, Badge, Avatar, RatingStars, Price, Tag, EmptyState, Skeleton, CallToAction.
Readme
@cool-ai/beach-a2ui-basics
Generic semantic primitives for A2UI v0.9 — extends @a2ui/web_core's basic catalogue with cross-domain components like Pill, Hero, Heading, Badge, Avatar, RatingStars, Price, Tag, EmptyState, Skeleton, CallToAction.
Home: cool-ai.org · Documentation: cool-ai.org/docs
What it is
A2UI v0.9's basic catalogue ships structural primitives — Card, Row, Text, List, Button, Image — intentionally close to raw HTML. This package is the generic semantic layer above it: components every Beach consumer will reuse regardless of domain.
Schema = contract; implementation = brand. Two consumers can implement the same Pill schema with totally different visuals; two agents emitting Pill to those consumers both render appropriately. The Beach catalogue is the agreed semantic vocabulary.
Domain-specific components (DestinationCard, TaskCard, EmailDigestCard) belong in consumer-owned catalogues that compose Beach primitives. Beach is the cross-domain coordinator, not the domain expert.
Install
npm install @cool-ai/beach-a2ui-basics @a2ui/lit @a2ui/web_core litQuick start
import { MessageProcessor } from '@a2ui/web_core/v0_9';
import { basicCatalog } from '@a2ui/web_core/v0_9/basic_catalog';
import { beachBasicsCatalog } from '@cool-ai/beach-a2ui-basics';
// Importing the element file registers the custom element with the browser.
import '@cool-ai/beach-a2ui-basics';
// Optional: import the default token palette.
import '@cool-ai/beach-a2ui-basics/tokens.css';
const processor = new MessageProcessor([basicCatalog, beachBasicsCatalog]);Then emit the catalogue's components in your surface messages:
{ component: 'Pill', label: 'Recommended', intent: 'recommended', icon: '✦' }Components
Pill
A small, rounded badge for status labels and category tags.
| Prop | Type | Required | Notes |
|------|------|----------|-------|
| label | DynamicString | yes | Text inside the pill |
| intent | DynamicString | no | 'neutral' \| 'recommended' \| 'success' \| 'warning' \| 'danger'. Unknown values render with neutral defaults |
| icon | DynamicString | no | Emoji, single character, or short text rendered before the label |
| class | DynamicString | no | Pass-through HTML class attribute on the host element |
Theming surfaces:
::part(label)— the inner label span::part(icon)— the inner icon span (only present wheniconis set)- HTML
classattribute via theclassschema prop - CSS custom properties:
--bca-pill-bg,--bca-pill-fg,--bca-pill-radius,--bca-pill-padding-block,--bca-pill-padding-inline,--bca-pill-gap,--bca-pill-font-size,--bca-pill-font-weight,--bca-pill-letter-spacing, plus--bca-pill-{recommended,success,warning,danger}-{bg,fg}
/* Brand override example */
.brand-pill::part(label) {
text-transform: uppercase;
letter-spacing: 0.1em;
}
bca-pill[intent='recommended'] {
--bca-pill-recommended-bg: var(--my-brand-gold);
--bca-pill-recommended-fg: var(--my-brand-navy);
}Show
Conditional rendering primitive for in-surface progressive disclosure. When when resolves truthy, the referenced child component renders; falsy, the slot renders empty. Re-evaluates on data-model changes; only the affected subtree re-renders.
| Prop | Type | Required | Notes |
|------|------|----------|-------|
| when | DynamicBoolean | yes | Literal true/false, JSON-pointer path ({ path: '/openSlug' }), or function call returning boolean |
| child | ComponentId | yes | The id of the child component to render when when is truthy. Must exist in the surface's components map (same convention as Card.child); referencing a non-existent id throws A2uiStateError |
A2UI v0.9 has three other conditional-shaped primitives; Show fills the gap left by the others:
| Situation | Use |
|---|---|
| Switch between N pre-defined states with tab chrome | Tabs (A2UI basic catalogue) |
| Overlay reveal that takes over the screen | Modal (A2UI basic catalogue) |
| Drill into a detail warranting a new surfaceId | subsurface:open (@cool-ai/beach-protocol) |
| Progressive disclosure / data-bound section toggle within one surface | Show |
// Toggle a detail panel by data-model state.
{ component: 'Show',
when: { path: '/expanded' },
child: 'detail-panel' }The host element uses display: contents so the conditional wrapper has no visual footprint — the rendered child sits in the parent's layout flow as if <beach-show> were not present.
Architectural notes
Single-catalog constraint. A2UI v0.9 binds a surface to one catalog (set on
createSurface). The renderer resolves every component against that single catalog. To combineShowfrom this catalogue with primitives from@a2ui/web_core's basic catalogue in the same surface, the consumer needs to construct a merged catalog. Same constraint applies to every consumer catalogue, not just Show.Import order for server-side rendering. Lit's
@customElementdecorator registers against the globalcustomElementsregistry at decorator time. When using Beach'srenderToHtml/renderToTextfrom@cool-ai/beach-a2ui/render-server, the element modules must be imported AFTERbootstrapServerRender()has provisioned jsdom — otherwise the registration no-ops:import { renderToHtml, bootstrapServerRender } from '@cool-ai/beach-a2ui/render-server'; await bootstrapServerRender(); await import('@cool-ai/beach-a2ui-basics'); // dynamic import after bootstrap const html = await renderToHtml(commands, { catalogs: [beachBasicsCatalog] });CAIB-243 will add a defensive validator that surfaces this as a loud error rather than silent-empty output.
Theming
Every CSS custom property is prefixed --bca-* (Beach Catalogue Atoms) so it coexists cleanly with @a2ui/web_core's --a2ui-* tokens. Override at :root, on a parent element, or per-instance via the class prop.
The default token palette ships in tokens.css. Importing it gives consumers an opinionated-but-overridable starting point — neutral surfaces, an amber accent, soft status colours, system-font typography. Override any subset by setting the custom properties on :root (or a scoped parent) after importing the defaults.
Catalogue identifier
import { BEACH_A2UI_BASICS_CATALOG_ID } from '@cool-ai/beach-a2ui-basics';
// 'https://cool-ai.org/a2ui/v0.9/basics.json'The URL is the contract. Bumps:
- The
/v0.9/segment moves when A2UI itself upgrades to a new protocol version. - A trailing
/vN/is added for schema-breaking changes within a protocol version. - Patch releases of this package do not change the URL.
Same posture as JSON Schema $id.
Why this layering
Beach owns the generic primitives so:
- Every Beach application speaks the same semantic vocabulary; cross-agent rendering "just works" once consumers register this catalogue.
- Domain-specific catalogues (TA's
DestinationCard, PO'sTaskCard) compose Beach primitives instead of reinventing them. - Brand customisation lives at the consumer side via tokens, parts, and class — not at the protocol or component level.
See https://cool-ai.org/docs/design-principles for the architectural framing.
Related
@cool-ai/beach-a2ui— the wider Beach A2UI surface (renderer, builders, types).@a2ui/web_core— the upstream A2UI framework.@a2ui/lit— the Lit-based reference renderer.
