Component blocks
Insert, edit, and troubleshoot the five component block primitives (Callout, Image, Video, Audio, Accordion) inside the Open Knowledge editor.
Open Knowledge ships a WYSIWYG editor that renders MDX components as live React components. You write your markdown or MDX on disk (or via the MCP write tools); the editor parses it, renders each component with its real implementation, and lets you edit props and children without leaving the visual canvas.
The 5-pack foundation
The editor ships five built-in component primitives, each with a researched prop surface and a DIY React renderer using Open Knowledge's own brand (shadcn/Tailwind). Every descriptor pairs an MDX form with a markdown form where a standard parse path exists, and round-trips byte-identically on pristine save.
| Component | Category | Markdown form | MDX form |
|---|---|---|---|
Callout | Content | > [!NOTE]\nText (GFM alerts; +/- suffix for foldable) | <Callout type="note" title="…" icon="lucide:…" color="#…" collapsible defaultOpen>…</Callout> |
img | Media |  (standard CommonMark) | <img src="…" alt="…" width={…} height={…} loading="lazy" srcset="…" sizes="…" /> |
video | Media | (no markdown form — HTML5 <video> passthrough) | <video src="…" controls autoplay muted loop playsinline poster="…" preload="…" width={…} height={…} /> |
audio | Media | (no markdown form — HTML5 <audio> passthrough) | <audio src="…" controls autoplay loop muted preload="…" /> |
Accordion | Content | <details open><summary>X</summary>Y</details> | <Accordion title="X" defaultOpen icon="lucide:…" description="…" id="…" name="…">Y</Accordion> |
Prop counts in this section include
childrenfor components that accept it (CalloutandAccordion). TheProps (N)count matches the number of typed entries in the component's prop panel + the children slot, so you can read it as "everything that lands in the editor surface."img,video, andaudiohave no children slot (they're self-closing leaves), so their counts reflect only typed props. The PropPanel partitions HTML-native attrs into a collapsed Advanced section by default — common props (e.g.src,alt,width,height) stay flat;srcset/sizes/decoding/fetchpriority/crossorigin/referrerpolicylive under Advanced.
Callout
GFM-style alert / admonition with five type variants and optional foldable chrome.
Common props (2): type (note | tip | important |
warning | caution, default note), title (string).
Advanced props (4, collapsed in PropPanel by default): icon
(namespaced lucide like lucide:Lightbulb), color (hex accent
override), collapsible (boolean), defaultOpen (boolean, default
true when collapsible).
Plus children (the body content).
<Callout type="warning">
Always run tests.
</Callout>> [!WARNING]
> Always run tests.> [!NOTE]-
> Click to reveal.> [!TIP]+
> Visible on first render.The foldable marker - maps to collapsible: true, defaultOpen: false;
+ maps to collapsible: true, defaultOpen: true. No suffix renders a
static Callout. The +/- marker is supported only inside the five
GFM types; broader Obsidian type syntax ([!success], [!idea], etc.)
folds to the closest GFM type via the parser alias map but does not
trigger foldable rendering unless the type is one of the GFM five.
Parser alias map. Common Obsidian / Mintlify types fold on disk to
their GFM-family closest (success → tip, danger → caution,
idea → tip, info → note, etc.). The fold is lossy-on-edit: the
authored form survives byte-identical on pristine save (the editor
keeps your original bytes as long as you haven't touched the block),
but an edit through the WYSIWYG / prop panel canonicalizes to the
folded GFM type. A broader alias-preserving type set is on the roadmap
and lands additively when it does — your existing content keeps
working.
Unknown type values fall back to note in both authoring forms.
Whether authored as <Callout type="custom"> MDX JSX or > [!custom]
GFM-alert, unrecognized type tokens render with note styling. The
authored token survives byte-identical on disk, and the alias-coerced
path (e.g. success → tip) additionally remembers the original
token — so when a future release extends the type set, content
authored today under the narrow surface automatically upgrades to
richer rendering without an edit pass.
Multi-word blockquote openers like > [!DOWN FOR MAINT] (tokens with
spaces) do NOT match the GFM-alert shape and stay as plain blockquotes
— an intentional carve-out for legitimate non-callout uses of bracketed
notation.
Image
Lowercase HTML <img> canonical with click-to-zoom always on.
Common props (2): src (required), alt.
Advanced props (10, collapsed in PropPanel by default): width
(number), height (number), srcset, sizes, loading ('eager'
| 'lazy', default 'lazy'), title (native HTML title tooltip),
decoding ('sync' | 'async' | 'auto', default 'auto'),
fetchpriority ('high' | 'low' | 'auto', default 'auto'),
crossorigin ('anonymous' | 'use-credentials'), referrerpolicy.
Pixel width / height are layout-shift-prevention specialists —
most authors lay out images via CSS or container width. Attribute
names are HTML-spec lowercase so emitted MDX matches the HTML spec
exactly; React translates to camelCase at the JSX boundary.
<img
src="/assets/diagram.png"
alt="Architecture"
width={640}
loading="lazy"
/><img
src="/assets/diagram.png"
alt="Architecture"
srcset="/assets/diagram@1x.png 1x, /assets/diagram@2x.png 2x"
sizes="(max-width: 600px) 400px, 800px"
/>Click-to-zoom uses react-medium-image-zoom with a native <dialog>
modal; the library honors prefers-reduced-motion internally. Zoom
is always on at the descriptor level — a future Frame v2 wrapper
(<Frame zoom={false}><img/></Frame>) is the planned opt-out path.
Authoring forms. Both CommonMark  and the lowercase
<img> MDX form render through descriptors —  parses
through the read-only CommonMarkImage compat descriptor and round-
trips byte-identical, while <img> is the canonical lowercase form
with the full HTML-attribute surface. Click-to-zoom is on by default
in both cases. To convert an existing CommonMark image and unlock
the full attribute surface, click the image, open the PropPanel, and
press Convert to Image — the PM node flips to the canonical img
descriptor on save. Both forms round-trip byte-identical until the
moment you edit. Obsidian ![[file.ext]] wiki-embed is a separate
parse path with its own renderer (out of scope for this guide).
Video
Pure HTML5 <video> wrapper with native controls. Self-closing leaf
(symmetric with <img>). Matches Mintlify's explicit-iframe pattern
for embedded services: no YouTube / Vimeo URL auto-promotion, no
iframe emission. Authors embedding service videos write a raw
<iframe> in MDX directly.
Common props (1): src (required).
Advanced props (10, collapsed in PropPanel by default): controls
(default true), autoplay, poster, width (number), height
(number), title (tooltip), muted, loop, playsinline, preload
('none' | 'metadata' | 'auto'). The fresh-insert form is a
single src field; toggle controls / autoplay / etc. from
Advanced if needed. Attribute names are HTML-spec lowercase
(autoplay, playsinline); React maps to camelCase (autoPlay,
playsInline) at the JSX boundary.
<video src="/clip.mp4" poster="/p.jpg" autoplay muted loop />Captions and codec fallback. For <track> caption sources or
<source> codec fallbacks, write a raw HTML5 <video> element with
children in MDX directly:
<video src="/clip.mp4" controls>
<track kind="captions" src="/clip.en.vtt" srclang="en" label="English" default />
<source src="/clip.webm" type="video/webm" />
</video>This takes the rawMdxFallback path — bytes preserved on disk, editable
as MDX source, renders natively in the browser. A typed tracks +
sources array prop on the video descriptor is tracked as future
work when ProseMirror's descriptor system grows an array-of-structured-
records prop type.
Audio
HTML5 <audio> wrapper. Self-closing leaf (symmetric with <video>).
Common props (1): src (required).
Advanced props (6, collapsed in PropPanel by default): controls
(default true), autoplay, title, muted, loop, preload
('none' | 'metadata' | 'auto'). Set controls={false} for
chrome-less playback (rare, mostly for hero loops). Attribute names
are HTML-spec lowercase; React maps autoplay to autoPlay at the
JSX boundary.
<audio src="/clip.mp3" autoplay loop preload="metadata" />For <source> codec fallback elements, write a raw <audio> element
in MDX (same escape hatch as <video> above) — descriptor-dispatched
<audio> has no children slot.
Accordion
Standalone expand/collapse built on native HTML5 <details> /
<summary>. No wrapper parent required; cross-browser exclusive
grouping via HTML5 <details name="…"> (Chrome 120+, Safari 17.2+,
Firefox 130+).
Common props (2): title (required), defaultOpen (boolean).
Advanced props (4, collapsed in PropPanel by default): icon
(namespaced lucide), description (subtitle), id (for deep-linking),
name (exclusive-group identifier).
Plus children (the body content).
<Accordion title="Advanced options" defaultOpen id="advanced">
- `--fetch-retry-maxtimeout=60000`
- `--loglevel=warn`
</Accordion><details open>
<summary>Advanced options</summary>
- `--fetch-retry-maxtimeout=60000`
- `--loglevel=warn`
</details><Accordion title="One" name="group-a">First answer</Accordion>
<Accordion title="Two" name="group-a">Second answer</Accordion>
<Accordion title="Three" name="group-a">Third answer</Accordion>Opening any accordion in the group closes the others via the browser's native behavior — no JavaScript state.
The slash menu
Type / at the start of an empty line to open the insertion menu.
Start typing to filter; pick an entry with the arrow keys and press
Enter to insert.
Every entry inserts a fully-formed MDX block with sensible defaults. For components with editable props, the settings panel opens automatically so you can fill in required fields before you move on.
When a media block (img, video, audio) is inserted without a
source, it renders as an "Add an image" / "Add a video" / "Add audio"
pill instead of a broken placeholder. Click the pill to reopen the
settings panel — typing a URL replaces it with the rendered media.
Editing props
When a component is selected, a hover chrome bar appears at the top-right with three groups of actions (rendered as up to four icon buttons depending on the block's position within its parent):
- Move up / Move down — reorder the component within its parent (one button per direction; only shown when the move is meaningful).
- Delete — remove the component and its children.
- Settings (gear) — open the floating settings panel.
The settings panel is auto-generated from the component's prop descriptor:
- String props render as text inputs.
- Boolean props render as toggle switches.
- Enum props render as
<select>dropdowns with the valid values. - Number props render as numeric inputs.
- React-node props (children) are edited in the canvas, not the panel.
Your edits write to the underlying MDX immediately — storage is the CRDT, and every keystroke propagates to other connected clients.
Keyboard shortcuts
| Action | Shortcut |
|---|---|
| Open settings panel (when a component is selected) | Enter or Space |
| Close settings panel and return focus to the component | Esc |
| Move selection between blocks | ↑ / ↓ |
| Move a top-level block up / down | Cmd+Shift+↑ / ↓ (macOS) · Ctrl+Shift+↑ / ↓ (Linux / Windows) |
Accessibility
Every built-in component renders with accessibility primitives baked into the selection layer rather than retrofitted per-block:
- Selection announcer. Selecting a block emits an
aria-live="polite"announcement with the block's descriptor name so screen readers narrate the focused element. - Breadcrumb ancestry. The selection footer renders an
aria-label-tagged list of ancestor components so keyboard users can jump to any enclosing container. - Halo palette. The block selection halo degrades to a solid outline
under Windows High Contrast Mode (
forced-colors: active) — no color information is lost. - Motion. Halo transitions honor
prefers-reduced-motionand collapse to instant state changes when the OS setting is on. Accordion's disclosure-triangle rotation honors the same preference. - Keyboard parity. Every hover-chrome action (move up / move down /
delete / settings) is reachable via
Tab→Enterfrom the selected block.
The block-selection a11y contract is enforced in the selection-state plugin rather than per-renderer, so new descriptors inherit it without opt-in code.
Authoring-form preservation
Components round-trip byte-identical on pristine save — content you haven't edited since parse serializes back to whatever form you wrote on disk. The editor's hybrid serialization preserves the authoring shape:
> [!NOTE]stays> [!NOTE].> [!WARNING]-stays> [!WARNING]-with the+/-marker intact.<details><summary>X</summary>Y</details>stays in that form.<Callout>…</Callout>stays in that form.stays as a standard CommonMark image.
When you edit a component in WYSIWYG (title change, prop toggle, …), the save canonicalizes to the MDX JSX form with the new props. The original form is only reconstructed if all props match and no children were touched.
Unknown attributes round-trip losslessly. Attrs not declared by the
descriptor (e.g. <Accordion variant="accent">, <Callout severity="critical">) are preserved on disk, ignored by the renderer,
and hidden from the prop panel. Narrowing a descriptor never corrupts
content authored against a broader descriptor elsewhere.
Unregistered and broken components
Open Knowledge's rule: all user content stays visible and editable. If a component cannot be rendered live, it falls through to an editable source-code block instead of disappearing.
Two situations trigger the fallback:
-
Unregistered component name. The editor ships live React renderers only for the 5-pack. Unknown names (your own project-specific components, imported libraries we don't bundle, or cut primitives like
<Tabs>/<Card>/<Steps>that lived in earlier iterations) render as an editable source block — a nested CodeMirror editor containing the raw source. Fix the source; on blur the editor re-parses and, if the source is valid MDX again, replaces the fallback with a live component. -
Render errors. A valid component that throws during React render (bad prop, missing required value, runtime crash) is caught by an error boundary and auto-converts to the same editable source block. The error message is shown in the chrome bar so you can diagnose what needs fixing.
If the auto-convert itself can't land after a few retries (rare, but possible under unusual conditions), the block surfaces a stuck-state banner with Copy source and Delete buttons. Use Copy source to move the MDX into a fresh location, then Delete the failed block. Either way: your MDX bytes are preserved exactly as you authored them. The raw source never disappears.
<Mermaid /> and other deprecated placeholders
Earlier versions shipped non-functional placeholders for <Mermaid />
and <AudioPlaceholder />. Those stubs were removed; existing content
auto-converts to the editable source fallback on first open. Rename
<AudioPlaceholder /> to <audio /> to pick up the built-in
audio-player descriptor.
Authoring via MCP / agents
Agents writing to the document through MCP's write_document /
edit_document tools produce markdown and MDX exactly as above. AI
agents tend to emit GFM alerts (> [!NOTE]) and standard MDX JSX
natively from their training distribution; both forms parse to the
same descriptors and round-trip faithfully.
If an agent writes an unregistered component name, the editor behaves exactly as for a human-authored unknown tag: the component appears as an editable source fallback, the MDX source is preserved, and on blur the fallback upgrades to a live component if the source now parses.
Future work
The 5-pack is the current foundation. A compound tier (Tabs / Tab,
an <Accordions> grouping wrapper for coordinated animation + shared
chrome, Steps / Step) is not built in today. Compound parent-child
primitives get re-added when dev-docs or help-center authoring demand
surfaces; no public API will change for existing 5-pack consumers.
Other names that don't have live renderers today: Card / CardGroup, Banner, Files-tree, TypeTable, InlineTOC, Math / KaTeX, Mermaid. All of these round-trip through your files byte-identical and render as editable source blocks until (and unless) a live renderer lands. User-registered custom components and live-rendered inline-component editing remain out of scope.
See also
- Getting started — install and first project.
- MCP integration — how agents author content.
- Configuration — what
.ok/contains and how path filtering works.
MCP integration
How the MCP server connects AI agents to your knowledge base — disk-only mode, Hocuspocus-connected mode, and available tools.
Install for Claude Chat & Cowork (Desktop App)
One-command install for getting the Open Knowledge skill into the Claude Desktop App so it's available in Claude Chat and Claude Cowork modes.