Core
Edit this page on GitHub →@basemark/core is the framework-agnostic engine everything else here is built on — a remark-directive parser, a component registry, and a render step. No component packs are bundled in; register whichever ones you actually need (@basemark/bio, @basemark/common, @basemark/charts, ...).
Install
pnpm add @basemark/core
The pipeline
Markdown text
│ remark-parse + remark-directive + remark-gfm
mdast
│ resolves each directive against the Component Registry,
│ validates its props against that component's schema
hast
│ render step (see below)
DOM / static HTML / React tree
Two things worth knowing before writing a real document, both covered on their own page:
- Authoring syntax — the
remark-directivegrammar itself: leaf/text/container directives, and the two failure modes worth knowing up front. - Tiering model — the design rule behind every component's prop list: never make an author supply a data blob when a short identifier is enough.
The registry
Every component — card, structure, bar-chart, all of them — is a name registered against a manifest: a prop schema, a domain, and a render target (a Web Component tag, by default). createRegistry() starts empty; each pack's register*Components() fills it in.
import { createRegistry } from '@basemark/core';
import { registerCommonComponents } from '@basemark/common';
const registry = createRegistry();
registerCommonComponents(registry);
A registry with nothing registered isn't an error — an unregistered directive just fails visibly instead (see Authoring syntax).
AI-facing prompts, generated not hand-written
generateSystemPrompt(registry) turns whatever's registered into an index an AI author can read directly — one line per component, no separate doc to keep in sync. describeComponent(registry, name) expands one entry to its full prop list on demand. Both come straight from the same registry your app renders with, so the prompt can never drift from what's actually available — and both are scoped to whatever packs you registered, not all of Basemark:
import { createRegistry, generateSystemPrompt, describeComponent } from '@basemark/core';
import { registerBioComponents } from '@basemark/bio';
const registry = createRegistry();
await registerBioComponents(registry);
generateSystemPrompt(registry); // index of every registered component
generateSystemPrompt(registry, { domain: 'bio' }); // scoped to one pack, if you registered several
describeComponent(registry, 'structure'); // one component's full prop list, on demand
This is what a Claude Skill (or any other AI-authoring surface) reaches for instead of a hand-maintained component list — see the introduction for that consumption path.
Rendering
Three render paths, one parser:
renderMarkdown(source, registry)— returns aDocumentFragmentof real DOM nodes to append yourself (browser only).renderMarkdownToHtml(source, registry)— returns a plain string, nodocumentrequired. Every narrative page on this site (including this one) is rendered this way.@basemark/react'sMarkdownRenderer— same parse, wrapped as a React component.
See Getting started for all three, with code.