@syncropel/react
v0.7.1
Published
React components + SRP renderer for the Syncropel protocol — a constrained palette of atoms and molecules plus a recursive renderer for Syncropel Rendering Protocol (SRP v0.2) documents, with tokens-customizable, structure-frozen defaults.
Maintainers
Readme
@syncropel/react
React components for the Syncropel protocol. A constrained palette of 20 atoms + 10 molecules with tokens-customizable, structure-frozen defaults, plus <SRPRenderer> — a recursive renderer for Syncropel Rendering Protocol (SRP v0.3) documents from @syncropel/projections. Equally usable on its own.
import "@syncropel/react/styles.css";
import { Card, Column, Heading, Stat, Grid } from "@syncropel/react";
export function Dashboard() {
return (
<Card padding="lg">
<Column gap="md">
<Heading level={2}>Session summary</Heading>
<Grid cols={3} gap="sm">
<Stat label="Tracks played" value={47} />
<Stat label="Artists" value={14} delta={3} />
<Stat label="Transitions" value={12} />
</Grid>
</Column>
</Card>
);
}Philosophy
Most design systems optimize for developer freedom: any prop, any override, any combination. This one optimizes for guarantees — the kind you need when a UI might be generated by an LLM or described in a JSON document and you still want it to render correctly.
- Tokens customizable. Rebrand colors, typography, spacing via CSS custom properties at
:root. No rebuild required. - Structure frozen. Component shapes and defaults are fixed. No
classNameescape hatches, no off-scale pixel values, no arbitrary variants. - Closed palette. Growth through reviewed spec proposals, not consumer-side overrides. 29 primitives today; new primitives are added deliberately, not opportunistically.
- Serializable by design. Every component's props can survive a JSON round-trip — which is exactly why the schema package (
@syncropel/projections) can describe them declaratively.
Install
npm install @syncropel/reactPeer dependencies: react >= 18, react-dom >= 18. Runtime dep: @syncropel/projections (for schema types used by Text's inline-markdown parser).
Import styles once at your app root:
// app/layout.tsx or pages/_app.tsx
import "@syncropel/react/styles.css";For non-React iframes that want the tokens without React:
<link rel="stylesheet" href="https://unpkg.com/@syncropel/react/dist/tokens.css" />What's in the palette
Atoms (19)
| Component | Role |
|---|---|
| Button | Text-label button — 4 variants, 3 sizes, loading affordance |
| IconButton | Icon-only button — ariaLabel required at type level |
| CopyButton | Click-to-copy with 1.5s confirmation |
| Chip | Compact metadata chip with optional leading glyph |
| Code | Monospace inline or block (<code> / <pre><code>) |
| Column | Vertical flex container |
| Divider | Horizontal / vertical separator |
| Glyph | Semantic Unicode symbol (acts, domain objects, thread states, AITL) |
| Grid | Two-dimensional grid (cols 1–12) |
| Heading | Semantic heading h1–h6 with enforced size mapping |
| KeyValue | Horizontal label + value row (settings, metadata) |
| Link | Navigational anchor with focus ring + hover underline |
| Pulse | Inline loading ellipsis |
| Row | Horizontal flex container |
| Segmented | Row of mutually-exclusive options — a filter/choice control |
| Select | Native single-choice input; host owns the value |
| Skeleton | Rectangular / circular / text loading placeholder |
| StatusDot | Colored state indicator dot |
| Text | Paragraph / inline text; parses inline markdown subset in string children |
| TextInput | Editable text field — single-line / multiline, search kind |
Molecules (10)
| Component | Role |
|---|---|
| Board | Kanban — records bucketed into columns by a field; optional drag |
| Card | Bordered container with token-scale padding; optional interactive state |
| DataTable | Columnar record list — explicit columns, alignment, in-component sort |
| EmptyState | Fallback when a list / region has no content |
| ErrorState | Error fallback with optional retry |
| Form | Input-grouping container with a submit affordance |
| RecordLine | One record rendered as a line — act glyph + title + metadata, by variant |
| RecordLineList | A vertical list of RecordLines with a shared variant |
| Stat | Label + big value + optional delta (dashboard-grade metric display) |
| Tabs | Selectable panels — a tab strip plus the active panel |
Rendering SRP documents
<SRPRenderer> walks a Syncropel Rendering Protocol (SRP v0.3) document — a declarative JSON UI tree — into this component palette. SRP owns layout and shape; your application owns state and behaviour, supplied through an SRPHost.
import "@syncropel/react/styles.css";
import { SRPRenderer, type SRPHost } from "@syncropel/react";
import type { SRPDocument } from "@syncropel/projections";
const doc: SRPDocument = {
srp: "0.2",
root: {
type: "column",
children: [
{ type: "heading", props: { level: 2, text: "Tasks" } },
{
type: "record-line-list",
props: { variant: "compact" },
bind: { query: { act: "INTEND" } },
},
],
},
};
const host: SRPHost = {
dispatch: (action) => console.log("intent", action.intent, action.payload),
resolveQuery: (query) => myCache.records(query), // host-cached, synchronous
resolveRecord: (id) => myCache.record(id),
};
export function Workspace() {
return <SRPRenderer doc={doc} host={host} />;
}bindis host-supplied state;actionsare intents the renderer dispatches back throughhost.dispatch(runtime values — the new select value, the clicked row — are merged into the action payload).- The document is validated before rendering; an invalid document renders an
ErrorState. Unknown node types render a graceful fallback.
Token customization
Override any value at :root (or any ancestor) to retheme:
:root {
--accent: #0ea5e9;
--bg-primary: #ffffff;
--text-primary: #111827;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
}Tokens cover: color palette (earth-mineral by default; copper accent), typography scale (7 sizes, 3 weights), spacing scale (8 units), motion durations, border radii. See dist/tokens.css for the full list.
Inline markdown in Text
<Text> parses a narrow markdown subset when children is a string — **bold**, *italic*, `code`, [link](url), ~~strike~~. This lets both humans and language models write expressive copy without nested React trees:
<Text>
Visit **example.com** or read the [docs](/docs) for `emit()` examples.
</Text>Renders with a bold span, a link (routed through <Link>), and an inline <Code> automatically.
Related packages
- @syncropel/sdk — emit + query records against the Syncropel protocol
- @syncropel/projections — declarative UI document schema; these components render it
See CHANGELOG.md for release notes.
