@oddball/json-render-uswds
v0.1.1
Published
USWDS-compliant component library for @json-render/core. JSON becomes federally-compliant React components powered by @trussworks/react-uswds.
Maintainers
Readme
@oddball/json-render-uswds
USWDS-styled component library for @json-render/core, powered by @trussworks/react-uswds. 48 federally-compliant React components plus a Zod catalog for AI-generated UI.
Install
pnpm add @oddball/json-render-uswdsPeer dependencies
pnpm add react react-dom zod@json-render/core, @json-render/react, and @trussworks/react-uswds are direct dependencies — no extra installs needed.
Stylesheet
Truss ships a compiled USWDS stylesheet. Import it once in your app entry:
import "@trussworks/react-uswds/lib/uswds.css";Or from CSS:
@import "@trussworks/react-uswds/lib/uswds.css";That's the whole styling story for this package — no Tailwind plugins, no token CSS, no PostCSS config required. (The example apps use Tailwind for their own chrome, but the components do not.)
A handful of Truss components (ComboBox, DatePicker, DateRangePicker, TimePicker, CharacterCount, TextInputMask, FileInput) need USWDS JS for full interactivity. Load @uswds/uswds on the page if you use them in production.
Usage
With json-render (the main path)
The package exports two parallel records keyed by identical component names:
uswdsComponentDefinitions—Record<string, { props: ZodSchema, slots?, description, example }>. Feed this todefineCatalog. AI agents validate generated specs against it.uswdsComponents—Record<string, ComponentType>. Feed this todefineRegistry.
// catalog.ts
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { uswdsComponentDefinitions } from "@oddball/json-render-uswds/catalog";
export const catalog = defineCatalog(schema, {
components: uswdsComponentDefinitions,
actions: {},
});// renderer.tsx
import {
Renderer,
StateProvider,
VisibilityProvider,
ActionProvider,
defineRegistry,
type Components,
type Spec,
} from "@json-render/react";
import { uswdsComponents } from "@oddball/json-render-uswds";
import { catalog } from "./catalog";
const { registry } = defineRegistry(catalog, {
components: uswdsComponents as unknown as Components<typeof catalog>,
});
export function Preview({ spec }: { spec: Spec }) {
return (
<StateProvider initialState={{}}>
<VisibilityProvider>
<ActionProvider>
<Renderer spec={spec} registry={registry} />
</ActionProvider>
</VisibilityProvider>
</StateProvider>
);
}The cast as unknown as Components<typeof catalog> is needed because the package exports uswdsComponents as a loose Record<string, ComponentType<any>>.
Guard partial specs. While streaming AI output, spec.root may not yet exist in spec.elements. The Renderer crashes in that state — check spec.elements[spec.root] before mounting. See examples/uswds-demo/src/components/SpecPreview.tsx and examples/uswds-playground/lib/render/renderer.tsx for the guard pattern.
Server-side validation only
import { uswdsComponentDefinitions } from "@oddball/json-render-uswds/catalog";
const result = uswdsComponentDefinitions.Button.props.safeParse(input);Standalone (without json-render)
Adapters accept plain React props as a passthrough, so you can use them like normal components:
import { uswdsComponents } from "@oddball/json-render-uswds";
const { Card, Alert, Button } = uswdsComponents;
export function App() {
return (
<Card heading="Hello">
<Alert type="info" heading="Welcome" text="Body copy here." headingLevel="h4" />
<Button type="button" variant="default">Submit</Button>
</Card>
);
}Spec format and text content
Specs are flat-tree ({ root, elements }). Element children are arrays of element keys, not inline strings — so text-bearing components take their content via a prop, not React children:
Text,Heading,Badge— usetextLink— uselabelAlert— useheading+text(no children; the body is a<p>and can't contain block elements)
{
"root": "page",
"elements": {
"page": { "type": "Card", "props": { "heading": "Status" }, "children": ["msg", "go"] },
"msg": { "type": "Alert", "props": { "type": "success", "text": "Saved.", "headingLevel": "h4" } },
"go": { "type": "Button", "props": { "variant": "default" }, "children": ["label"] },
"label": { "type": "Text", "props": { "text": "Continue" } }
}
}Components (48)
Layout & structure: Card, Section, Grid, GridContainer, Collection, MediaBlock, SummaryBox
Content: Heading, Text, Alert, SiteAlert, Badge, Link, Icon, IconList, Table, ProcessList, StepIndicator
Actions: Button, ButtonGroup
Forms: Input, Textarea, Select, Checkbox, Radio, ComboBox, DatePicker, DateRangePicker, TimePicker, FileInput, RangeInput, Search, Label, FormGroup, ErrorMessage, CharacterCount, TextInputMask
Navigation: Header, Footer, Breadcrumb, SideNav, InPageNavigation, Pagination, Accordion, LanguageSelector
Overlays & feedback: Modal, Tooltip, Banner, Identifier
Removed in the Truss migration. The CVA-era components (Avatar, Carousel, Collapsible, Dialog, Drawer, DropdownMenu, Image, Popover, Progress, Separator, Skeleton, Slider, Spinner, Stack, Switch, Tabs, ToggleGroup, Toggle) are not part of USWDS and were dropped. The list is exported as
UNSUPPORTED_COMPONENTSfor migration tooling.
Tests
304 tests covering per-component smoke renders, axe accessibility, the catalog ↔ components contract, and the json-render envelope passthrough (the only test path exercising the real production code path through defineRegistry).
pnpm testLicense
Apache-2.0. See LICENSE.
