Card Tree
betav0.5.0JSON-driven recursive card tree with a full structural editor — drag and drop, multi-select, permissions, search, validation, and undo.
Context
Card Tree renders deeply nested structured content — agent transcripts, configuration trees, decision records, runbooks, requirement docs — as a card-tree where each card has typed scalar flat fields (string/number/boolean/null/date), five predefined content blocks (codearea, image, table, quote, list), child cards, and per-card meta. v0.4 completes the safety net: sync validation hooks via 3-layer pipeline (built-in → per-action → master) with `onValidationFailed` event, plus per-commit undo/redo (state-snapshot strategy with structural sharing, default 50-step history, `Cmd+Z` / `Cmd+Shift+Z` / `Cmd+Y` keyboard shortcuts, optional `<CardTreeUndoToolbar>` sibling export). Markdown adapter (v0.5) deferred indefinitely as a separate companion module — card-tree itself is JSON-native.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/card-treeAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/card-tree-fixturesPreview
v0.4 demo: validators + per-commit undo/redo on v0.3's structural management foundation. Toggle edit mode to enable inline editing, drag-drop reordering, multi-select (shift-click range, cmd/ctrl-click toggle), the bulk toolbar (≥2 selected), and the undo toolbar (Cmd+Z / Cmd+Shift+Z / Cmd+Y). Use the search bar to find content in collapsed subtrees and meta entries.
Preview
v0.3 · 6 levels · all featuresThesis outline
- title
- Adaptive UI Components for Data-Heavy Applications
- abstract
- A study of dynamic component patterns for hierarchical structured content in modern web apps.
- word_count
- 28400
- status
- in-progress
- approved
- true
- defense_date
- Jun 15, 2026, 2:00 PM
- last_edited
- Apr 28, 2026
- reviewer_count
- —
- structured-content
- tree-rendering
- json-native
- accessibility
- round-trip
Architecture diagram cover introduction
- pages
- 8
- completed
- true
The web demands components that adapt to their data, not the other way around.
motivation
- summary
- Why hierarchical structured content needs a first-class viewer
- relevance_score
- 8.5
- addresses_gap
- true
component-class static-shape adaptive-shape table ✓ — card-tree — ✓ form-builder ✓ — json-tree — ✓ industry_need
- summary
- Industry surveys 2024-2026
- sample_size
- 1240
- confidence
- 0.95
- peer_reviewed
- false
ts interface AdaptiveProps<T> { data: T; schema?: Schema<T>; // shape inferred when schema is absent }survey_result
- finding
- 73% of devs build custom tree-card UIs per project
- methodology
- online survey + 14 interviews
- response_rate
- 0.42
- completed
- true
- shadcn covers primitives, not compositions
- json-tree libs render as code, not content
- Notion-likes lock you into a block schema
top_excerpt
- attribution
- Senior FE, public-traded SaaS
- interview_id
- 7
- recorded
- Jan 22, 2026, 4:30 PM
We rebuild this same hierarchical view for every product. Six weeks each, accessibility skipped, never reusable.
contributions
- summary
- What this thesis adds to the field
- novel_findings
- 3
- JSON-native data model with stable identity keys
- ARIA tree contract from day one
- Round-trip-safe serialization at every depth
conclusion
- pages
- 4
- written
- false
- summary
- Bring it home
methodology
- pages
- 12
- approach
- design + implementation + evaluation
Three components, three teams, six weeks each — measured against a single shared baseline.
results
- pages
- 18
- significant
- true
- p_value
- 0.003
- effect_size
- 0.81
metric baseline card-tree delta dev-time-days 21 2 -90% a11y-score 76 98 +29% bundle-kb 45 18 -60% round-trip-fidelity — ✓ —
Live playground
card-tree renders a recursive card tree from a JSON CardTreeJsonNode (scalar keys are fields, nested objects are subcards, __rcmeta holds metadata). Edit it on the left, press Submit, and the editable card renders on the right.
23 lines · valid
Nothing rendered yet
Edit the JSON on the left, then press Submit to render the live result on the right.
Demo source
Usage
When to use
Reach for CardTree when you have JSON-shaped, deeply nested, structured content — agent transcripts, configuration trees, decision records, runbooks, requirement docs — and want a card-tree view with typed-scalar fields, predefined content blocks (code, image, table, quote, list), and full keyboard accessibility.
Skip it for prose-only writing (use a markdown editor) or flat lists (use a table). Markdown source is not supported in v0.1 — card-tree is JSON-native.
Basic example
import { CardTree } from "@/components/card-tree";
export function Example() {
return (
<CardTree
defaultValue={{
title: "ADR-0042",
status: "accepted",
priority: 2,
codearea: { format: "ts", content: "const x = 1;" },
context: { reason: "..." },
}}
/>
);
}Reserved keys
__rcid, __rcorder, and __rcmeta are reserved. __rcid auto-generates via crypto.randomUUID() if absent. __rcorder controls sibling order (integer, gaps allowed). __rcmeta is a per-card scalar map exposed via the metaPresentation prop.
Predefined keys
Five reserved-name fields render as styled blocks:
codearea—{ format, content }image—{ src, alt? }table—{ headers: string[], rows: scalar[][] }quote— a stringlist— an array of scalars
Add a key to disabledPredefinedKeys to opt out — the parser then treats it as a flat field instead.
Field value typing
Flat-field values are JSON scalars: string, number, boolean, null. Type is inferred at parse time and rendered per type (numbers right-aligned mono; booleans as check / dash icons; ISO-8601 date strings formatted via Intl.DateTimeFormat; null as a muted em-dash).
Pass dateDetection="never" to disable date inference, or a custom predicate function for fine control.
Children
Any non-reserved, non-predefined property whose value is a plain object becomes a child card. Arrays of objects are rejected in v0.1 — convert to object-keyed form (e.g. { items: { item_0: a, item_1: b } }) or use the list predefined key for scalar arrays.
State model
The component is uncontrolled: defaultValue is the seed. To reset, remount via the key prop. Read the current state imperatively via a ref:
const ref = useRef<CardTreeHandle>(null);
// ...
const json = ref.current?.getValue(); // canonical JSON string
const tree = ref.current?.getTree(); // object form with auto-IDsAccessibility
The tree implements the full ARIA tree contract: role="tree", role="treeitem", aria-level, aria-expanded. Keyboard: arrows navigate visible cards, → expands / descends, ← collapses / ascends, Home / End jump to first / last, Enter / Space toggles collapse on a card with children.
Features
- JSON-native: accepts any plain object as a card; auto-attaches __rcid + __rcorder
- Typed flat-field rendering: numbers right-aligned mono, booleans as icons, ISO-8601 dates formatted
- Five predefined-key content blocks (codearea, image, table, quote, list) + custom-key registration
- Per-level + per-predefined-key slot styling
- Full ARIA tree contract with keyboard nav (arrows, home/end, expand/collapse, multi-select)
- Three meta presentation modes (hidden, inline, popover) with custom renderers + audit trail
- Inline editor: click-to-edit fields, keys, titles, predefined blocks, and meta entries
- Drag-drop reordering with 2 scopes (same-level + cross-level), keyboard alternative via @dnd-kit
- Multi-select with shift-click range + cmd-click toggle; bulk delete / duplicate / set-field / toggle-lock
- Permission matrix with declarative shorthand + 11 predicate escape hatches; meta-locked cascade
- Native data-model search: finds matches in collapsed cards and meta — auto-expands path
- Configurable delete policy (cascade / promote) + collision strategy (suffix / qualify / reject)
- Root-removal opt-in with onRootRemoved callback + emptyTreeRenderer prop
- v0.4 sync validation hooks (per-action + master); onValidationFailed event for analytics
- v0.4 per-commit undo/redo with state-snapshot (default 50-step history) + Cmd+Z keyboard binding + optional UndoToolbar sibling export
- Imperative handle: getValue / getTree / isDirty / markClean / setSelection / focusCard / addCardAt / removeCard / replaceRoot / getEffectivePermissions / findNext / findPrevious / scrollToMatch / clearSearch / undo / redo / canUndo / canRedo / clearHistory