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

FieldCompletionLintHover
name, kind::name / :name menusunknown component (with "did you mean"), inline used as block✓
propsprops, values, :bound and ::model variants, YAML props keysunknown prop, value not in enum, missing requiredprop docs
slots#slot menu and the block menu inside the componentunknown slot✓
childrenfilters the components offered inside
scopeloop variablesbinding paths
group, description, example, docssections, 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:

  1. plugin manifests (::if/::for from binding, the ecosystem plugins…)
  2. the components option
  3. 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.

Copyright © 2026