Components
Manifest
Describe your components once; completion, hover, lint and agents use it.
import { comark, defineComponents } from 'comark-codemirror'
const components = defineComponents([
{
name: 'card',
kind: 'block', // 'block' | 'inline' | 'both'
group: 'Layout', // block menu section
description: 'A content card',
props: {
title: { type: 'string', required: true, description: 'Card title' },
variant: { enum: ['soft', 'outline'], default: 'soft' },
flat: { type: 'boolean' },
value: { type: 'string', model: true }, // accepts ::value="data.x"
raw: { type: 'string', bindable: false }, // no :raw binding
},
slots: [{ name: 'default' }, { name: 'footer', description: 'Bottom area' }],
children: ['card-item'], // optional: only these components inside
example: '::card{title="Hello"}\nBody\n::',
docs: 'https://example.com/card',
},
])
comark({ components })
What each field drives
| Field | Completion | Lint | Hover |
|---|---|---|---|
name, kind | ::name / :name menus | unknown component (with "did you mean"), inline used as block | ✓ |
props | props, values, :bound and ::model variants, YAML props keys | unknown prop, value not in enum, missing required | prop docs |
slots | #slot menu and the block menu inside the component | unknown slot | ✓ |
children | filters the components offered inside | ||
scope | loop variables | binding paths | |
group, description, example, docs | sections, details and the docs panel | ✓ |
Props are keyed by name without prefixes: :title and ::title are derived from bindable (default true) and model (default false).
A boolean prop is inserted bare (flat), which comark reads as true.
Sources and merging
Manifests merge by name, later wins, props merged:
- plugin manifests (
::if/::forfrom binding, the ecosystem plugins…) - the
componentsoption - the Vite plugin's virtual module, passed as
components
Components the manifest does not know but the document uses are still offered, in a Used in this document section.