Package Exports
- @gemara/react
- @gemara/react/capability-catalog
- @gemara/react/control-catalog
- @gemara/react/guidance-catalog
- @gemara/react/interactive
- @gemara/react/primitives
- @gemara/react/principle-catalog
- @gemara/react/provider
- @gemara/react/threat-catalog
- @gemara/react/types
- @gemara/react/vector-catalog
Readme
@gemara/react
Headless React renderers for Gemara artifacts.
- Server-component-clean by default — no
useState, nouseEffect, no"use client"in the catalog renderers. - Interactive components (e.g.
CollapsibleGroup) live behind a separate subpath export so RSC consumers never pay the client-bundle cost. - Headless: every renderer emits semantic HTML with
data-gemara-*attributes. You bring the CSS. - Tree-shakeable per artifact via subpath imports.
Install
npm install @gemara/reactRequires react >= 18.
Quick start (Astro)
---
import { readFileSync } from "node:fs";
import { parse as parseYaml } from "yaml";
import { ControlCatalog } from "@gemara/react/control-catalog";
import { GemaraProvider } from "@gemara/react/provider";
import { isControlCatalog } from "@gemara/react";
const raw = readFileSync("./content/my-catalog.yaml", "utf8");
const data = parseYaml(raw);
if (!isControlCatalog(data)) throw new Error("Not a ControlCatalog");
---
<html lang="en">
<body>
<h1>My catalog</h1>
<GemaraProvider>
<ControlCatalog data={data} headingLevel={2} />
</GemaraProvider>
</body>
</html>The library does not ship a YAML/JSON loader — consumers parse with their tool of choice. The runtime guards (isControlCatalog, isGuidanceCatalog, isCapabilityCatalog, isPrincipleCatalog, isThreatCatalog, isVectorCatalog, and detectArtifactType) narrow the discriminated union.
Subpath imports
Renderers are deliberately not in the root barrel — each lives behind its own subpath so consumers tree-shake to exactly what they use.
| Import | What it gives you |
|---|---|
@gemara/react |
GemaraProvider, primitives, type guards, types |
@gemara/react/control-catalog |
ControlCatalog renderer + compound parts (Layer 2) |
@gemara/react/guidance-catalog |
GuidanceCatalog renderer + compound parts (Layer 1) |
@gemara/react/capability-catalog |
CapabilityCatalog renderer + compound parts (Layer 2) |
@gemara/react/principle-catalog |
PrincipleCatalog renderer + compound parts (Layer 1) |
@gemara/react/threat-catalog |
ThreatCatalog renderer + compound parts (Layer 2) |
@gemara/react/vector-catalog |
VectorCatalog renderer + compound parts (Layer 1) |
@gemara/react/primitives |
ArtifactRef, EntityRef, DateTime, Prose, Heading, HeadingScope |
@gemara/react/provider |
GemaraProvider, useLinkResolver, ArtifactReference, LinkResolver |
@gemara/react/interactive |
CollapsibleGroup (carries "use client") |
@gemara/react/types |
Raw Schemas and discriminated artifact types |
Styling: the data-gemara-* taxonomy
The library ships no CSS. The public styling contract is the set of data-gemara-* attributes emitted on rendered elements.
data-gemara-artifact="ControlCatalog" | "GuidanceCatalog" | "CapabilityCatalog" | "PrincipleCatalog" | "ThreatCatalog" | "VectorCatalog"on each renderer's root<article>.data-gemara-id— the artifactmetadata.id.data-gemara-part="header" | "title" | "meta" | "groups" | "group" | "control" | "guideline" | "capability" | "principle" | "threat" | "vector" | "control-id" | "control-title" | "guideline-id" | "guideline-title" | "capability-id" | "capability-title" | "principle-id" | "principle-title" | "threat-id" | "threat-title" | "vector-id" | "vector-title" | "objective" | "rationale" | "description" | "requirements" | "requirement" | "applicability" | "actors" | "actor" | "mappings" | "control-list" | "guideline-list" | "capability-list" | "principle-list" | "threat-list" | "vector-list" | "front-matter" | "extends"for structural slots.data-gemara-control-id,data-gemara-guideline-id,data-gemara-capability-id,data-gemara-principle-id,data-gemara-threat-id,data-gemara-vector-id,data-gemara-group-id,data-gemara-requirement-idfor stable selectors.data-gemara-mappings-label="guidelines" | "threats" | "principles" | "capabilities" | "vectors"on mapping sections.data-gemara-ref="artifact" | "entry" | "mapping-reference"+data-gemara-ref-idon resolver output.data-gemara-prose=""on theProsewrapper element (plain-text fields).
These attributes are stable across patch releases. Treat them like a CSS API.
Composing into your page outline: headingLevel
Every catalog renderer accepts an optional headingLevel prop (default 1) that sets the level of the catalog title. Nested sections add fixed offsets (group = +1, entry = +2, subsections = +3) and are clamped at <h6>.
Set headingLevel={2} when the host page already owns the <h1>.
<ControlCatalog data={data} headingLevel={2} />Customizing links: linkResolver
Cross-references (extends, imports, inline mapping entries) route through a single resolver provided via context. The default emits a plain <a> when a url is present and an inert <span> otherwise. Override it to plug in your router's <Link>, Astro's <a>, or anything else.
import { GemaraProvider } from "@gemara/react/provider";
<GemaraProvider
linkResolver={(ref, children) => (
<a href={`/control/${ref.referenceId}#${ref.id}`}>{children}</a>
)}
>
<ControlCatalog data={data} />
</GemaraProvider>The resolver receives an ArtifactReference (kind: "artifact" | "entry" | "mapping-reference", plus id, optional referenceId, url, relation).
Prose fields are plain text
The catalog text fields (objective, description, front-matter, recommendations.text, etc.) are rendered as plain text with white-space: pre-wrap so authored line breaks survive. The Gemara CUE schema does not type any of these as Markdown — that is a convention, not a contract — so the library makes no parsing promise. If a consumer needs rich formatting, swap the Prose primitive locally or pre-render upstream (e.g. in go-gemara) and feed pre-rendered output through your own wrapper.
React Server Components
The default catalog renderers and all primitives are server-component-safe. The only interactive component (CollapsibleGroup) lives in @gemara/react/interactive and carries a "use client" directive in its built output — RSC bundlers route it into the client graph automatically.
Spec version
This package is pinned to a specific Gemara schema release. The version lives in .spec-version at the repo root. Bumping it triggers a regeneration of src/generated/types.ts (CI fails on drift).
License
Apache-2.0. See LICENSE.