Rendering & content
Edit document-level metadata
Add typed metaFields to your schema for frontmatter-style data such as title, author, publish date, or tags. Metadata is stored as JSON on the top-level document meta field and is not serialized to YAML.
<script lang="ts">
import {
DocumentMetaPanel,
Editor,
createDocumentMetaController,
createSchema
} from 'uncial';
const schema = createSchema(blocks, {
metaFields: {
title: { default: '', required: true },
author: { default: '' },
publishedAt: { default: '', placeholder: 'YYYY-MM-DD' },
tags: { default: [], input: 'json' }
}
});
const metaController = createDocumentMetaController(schema.metaFields);
let document = $state({ type: 'doc', content: [{ type: 'paragraph' }] });
let meta = $state({ title: 'Hello world', author: 'Ada' });
</script>
<Editor {blocks} {schema} {metaController} bind:json={document} bind:meta />
<DocumentMetaPanel
controller={metaController}
fields={schema.metaFields}
onCommit={(nextMeta) => (meta = nextMeta)}
/>When metadata fields are configured, Editor also shows a built-in Metadata button in the toolbar. The renderer normalizes the same metadata and exposes it as an optional snippet prop.
<Renderer content={document} {blocks} {schema}>
{#snippet meta(meta)}
<h1>{meta?.title}</h1>
{/snippet}
</Renderer>Match Uncial to your site
Uncial's editor and renderer chrome are plain CSS. Tailwind and DaisyUI are not required by the library. Import the default styles once, then override --uncial-* custom properties anywhere above the editor in the cascade.
// Full default editor, renderer, controls, and rich text prose styles.
import 'uncial/styles';
// Or import only the editor chrome if your app owns prose typography.
import 'uncial/styles/chrome';Scope theme tokens to a wrapper when one page should look different from the rest of your app:
.your-custom-class .uncial-editor-shell,
.your-custom-class .uncial-renderer {
--uncial-color-surface: white;
--uncial-color-primary: #7c3aed;
--uncial-color-primary-contrast: white;
--uncial-color-accent: #d97706;
--uncial-radius-md: 0.5rem;
--uncial-font-body: Inter, system-ui, sans-serif;
--uncial-font-display: Georgia, serif;
--uncial-font-mono: 'IBM Plex Mono', ui-monospace, monospace;
}The most common tokens are:
--uncial-color-primary— Primary buttons, links, active controls, block focus outlines.--uncial-color-primary-contrast— Text/icons on primary buttons.--uncial-color-accent— Secondary emphasis color for consumers who want one.--uncial-color-surface— Main editor and renderer background.--uncial-color-surface-elevated— Toolbar, inputs, nested block surfaces.--uncial-color-border— Control, panel, renderer, and block chrome borders.--uncial-color-text— Editor UI and rich text foreground.--uncial-color-code-bg— Fenced code block background. Defaults dark in both light and dark themes.--uncial-radius-sm/md/lg— Buttons, inputs, panels, renderer, and block chrome radius.--uncial-font-body/display/mono— Editor UI, headings, and code typography.
Rich text uses a stable .uncial-rich-content hook. If your site already has article typography, import uncial/styles/chrome and style that hook yourself:
.your-custom-class .uncial-rich-content {
font: 400 1rem/1.7 var(--site-body-font);
color: var(--site-ink);
}
.your-custom-class .uncial-rich-content h2 {
font: 700 1.75rem/1.2 var(--site-display-font);
margin-block: 2rem 0.75rem;
}Normalize and validate before publish
Use normalizeDocument and validateDocument around persistence boundaries, or pass onIssue to Editor and Renderer to observe document issues as they happen.
// src/lib/publish.ts
import { normalizeDocument, validateDocument } from 'uncial';
import { blocks, schema } from './uncial';
const normalized = normalizeDocument(document, blocks, schema);
const result = validateDocument(normalized, blocks, schema, {
onIssue: (issue) => console.warn(issue.code, issue.path)
});
if (!result.ok) {
throw new Error('Document is not publishable');
}Render on the server without the editor
"Same component in editor and frontend" only works if the frontend half stays lean. The Renderer is safe to import in a server bundle. Use the granular entry points so the browser-only editor stack (Tiptap, node views, mount()) never reaches your server.
// Server-safe: no editor mount code enters your bundle.
import { Renderer } from 'uncial/render';
import { createBlockRegistry, createSchema, defineSvelteBlock } from 'uncial/core';uncial/render and uncial/core are free of editor machinery, and a regression test walks the render/ import graph to keep it that way. The package root (import ... from 'uncial') also re-exports the editor, so prefer these entry points on the server.
Image attributes need one extra step when your site is served under a base path, such as a GitHub Pages project site. Stored image URLs start at the site root (/media/photo.webp), so add the base when you render them with resolveImageSrc:
<!-- src/lib/blocks/Figure.svelte -->
<script lang="ts">
import { base } from '$app/paths';
import { resolveImageSrc } from 'uncial/render';
let { src = '', alt = '' } = $props();
</script>
{#if src}<img src={resolveImageSrc(src, base)} {alt} />{/if}resolveImageSrc only changes URLs that start with a single /. Full URLs, data: URLs and the editor's blob: previews pass through unchanged, so the same component works in the editor and on the live site.