@tabula-css/react
Optional Tabula paved-path primitives: <Text>, <Separator>, <Prose>. Registry-backed merge; no Tailwind in the import graph.
Install
npm install @tabula-css/reactRuntime dependency — unlike the dev-side tooling packages, @tabula-css/react ships components your app renders. It depends only on @tabula-css/merge (react is a peer, ^18.0.0 || ^19.0.0), so — like the rest of the runtime — Tailwind never appears in its import graph.
Overview
These three components are optional: nothing in the profile requires React, and each one is a convenience over markup you could write by hand. Each exists because the profile bans something and owes a replacement for it — <Separator> replaces the banned divide-* family with a real element between children, <Text> is the closed type-*/ink-* text leaf in place of unregistered partial-typography utilities (text-sm, font-medium), and <Prose> is the quarantine boundary for un-authored HTML (rendered markdown, CMS output) that the profile explicitly declines to manage. Every primitive forwards its ref and merges the caller's className last through cn(), so a caller override always wins deterministically.
Text
Renders a single text leaf carrying exactly one type-* bundle and one ink-* color — the only classes permitted to write an inheritable typography property. as is a closed list of text-leaf tags on purpose: allowing an arbitrary container would reintroduce the ancestor-inheritance problem the component exists to remove.
| Prop | Type | Default | Description |
|---|---|---|---|
as | 'p' | 'span' | 'label' | 'strong' | 'em' | 'small' | 'h1'..'h6' | 'legend' | 'figcaption' | 'p' | The rendered tag. |
variant | 'body' | 'label' | 'small' | 'heading' | 'body' | Maps to type-body / type-label / type-small / type-heading. |
tone | 'text' | 'muted' | 'accent' | 'danger' | 'on-accent' | 'text' | Maps to ink-text / ink-text-muted / ink-accent / ink-danger / ink-on-accent. |
htmlFor | string | — | Forwarded to for when as="label". |
className | ClassName | — | Pass-through class, merged last through cn(). |
Plus every React.HTMLAttributes<HTMLElement> prop.
Requires these tokens to exist in your project: type.body, type.label, type.small, type.heading, color.text, color.text-muted, color.accent, color.danger, color.on-accent.
import { Text } from '@tabula-css/react';
<Text as="h2" variant="heading" tone="text">Section title</Text>
<Text variant="small" tone="muted">Last updated 3 days ago</Text>Separator
Renders a real element between children in place of divide-* (which styles siblings from the parent and is a banned non-local mechanism). Each child's own spacing then lives in the child's own markup.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | Maps to bg-border w-full h-hairline / bg-border w-hairline h-full; also sets aria-orientation. |
decorative | boolean | false | Renders role="none" with no aria-orientation for a purely visual rule whose grouping is conveyed elsewhere. |
className | ClassName | — | Pass-through class, merged last through cn(). |
Plus every React.HTMLAttributes<HTMLDivElement> prop.
Requires these tokens: color.border, size.hairline (1px), size.full (100%).
import { Separator } from '@tabula-css/react';
<Separator />
<Separator orientation="vertical" decorative />Prose
Marks a subtree the profile does not manage — an honest no-claim, not a style. Inside the boundary, locality is explicitly suspended: resolve_element (see @tabula-css/mcp) answers { managed: false, reason: "prose-quarantine" } for anything inside it. Every element child is auto-fenced with the registered not-prose marker utility so the generated reset can exclude the subtree via its one sanctioned ancestor combinator; a component child must forward className to its root for the fence to land.
| Prop | Type | Default | Description |
|---|---|---|---|
html | string | — | Un-authored HTML to render inside the boundary (the markdown/CMS case). Mutually exclusive with children. |
children | ReactNode | — | Authored children; every element child is auto-fenced. Mutually exclusive with html. |
as | 'article' | 'div' | 'section' | 'aside' | 'div' | The rendered tag. |
className | ClassName | — | Layout/box utilities only (w-, max-w-, m*, p*) — merged last. In development, any other utility throws, since a boundary must not set anything that inherits into content it declines to manage. |
import { Prose } from '@tabula-css/react';
<Prose html={renderedMarkdownHtml} className="max-w-prose" />
<Prose>
<p>Authored paragraph, auto-fenced with <code>not-prose</code>.</p>
</Prose>Also exported: useInQuarantine() — a hook returning whether the calling component is rendered inside a <Prose> boundary — and the QUARANTINE_ATTRIBUTE/QUARANTINE_KIND constants the boundary sets, re-exported from @tabula-css/core so the component and any tooling read the same values.
In development, <Prose> throws rather than render silently-wrong markup: when nested inside another <Prose> boundary (a quarantine cannot be re-nested), when both html and children are passed, or when className carries anything outside the box/layout allow-list.
See also
@tabula-css/merge— thecn()/ClassNamethese components build on.- Concepts — banned mechanisms — why
divide-*and partial typography utilities are banned. - Agent surface —
resolve_element'smanaged: falsequarantine reporting.