Skip to content

@tabula-css/react

Optional Tabula paved-path primitives: <Text>, <Separator>, <Prose>. Registry-backed merge; no Tailwind in the import graph.

Install

bash
npm install @tabula-css/react

Runtime 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.

PropTypeDefaultDescription
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.
htmlForstringForwarded to for when as="label".
classNameClassNamePass-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.

tsx
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.

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Maps to bg-border w-full h-hairline / bg-border w-hairline h-full; also sets aria-orientation.
decorativebooleanfalseRenders role="none" with no aria-orientation for a purely visual rule whose grouping is conveyed elsewhere.
classNameClassNamePass-through class, merged last through cn().

Plus every React.HTMLAttributes<HTMLDivElement> prop.

Requires these tokens: color.border, size.hairline (1px), size.full (100%).

tsx
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.

PropTypeDefaultDescription
htmlstringUn-authored HTML to render inside the boundary (the markdown/CMS case). Mutually exclusive with children.
childrenReactNodeAuthored children; every element child is auto-fenced. Mutually exclusive with html.
as'article' | 'div' | 'section' | 'aside''div'The rendered tag.
classNameClassNameLayout/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.
tsx
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

Released under the MIT License.