# UI and themes

Connect your design system while keeping source, commands, and components within their own boundaries.

## Your application supplies the appearance

The MSFE core does not depend on QuireKit's Vue components. Applications may choose their own component library; this documentation and its examples use QuireKit's public components and design tokens.

The examples install a theme and syntax highlighting through `addCodeMirrorExtensions()` in `shared/appearance.ts`. Open Integration code to see the complete theme definition.

### basic/editor.vue

```vue
<script setup lang="ts">
import { computed, ref, shallowRef } from "vue";
import { useMsfeEditor, VueNodeViewHost } from "@quirekit/msfe-vue";
import { StarterKit } from "@quirekit/msfe-starter-kit";
import { QuireButton, QuireSegmented, QuireProseSource } from "@quirekit/ui/vue";
import { Appearance } from "../shared/appearance";
import { exampleMessages } from "../shared/messages";
import type { ExampleLocale } from "../shared/types";

const props = withDefaults(defineProps<{ /** Language supplied by the application. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const text = computed(() => exampleMessages[props.locale]);
const initial = text.value.initial;
const parent = shallowRef<HTMLElement | null>(null);
const mode = ref("live");
const markdown = ref<string>();
const items = computed(() => [{ value: "live", label: text.value.live }, { value: "source", label: text.value.source }, { value: "locked", label: text.value.locked }]);

// One editor instance owns source, selection and history for all three modes.
const { editor, host } = useMsfeEditor(parent, () => ({ markdown: initial, mode: "live",
  extensions: [StarterKit, Appearance], label: text.value.editor }));

/** @param value - Mode selected in the shared control. */
function changeMode(value: string | null | undefined): void {
  if (value === "source" || value === "live" || value === "locked") { mode.value = value; editor.value?.setMode(value); }
}
/** Serializes only for an explicit save/read action; typing never mirrors the entire source. */
function readMarkdown(): void { markdown.value = editor.value?.getMarkdown(); }
/** Starts a fresh document and history after the user explicitly resets this example. */
function reset(): void { editor.value?.replaceDocument(initial, "example-reset"); markdown.value = undefined; }
</script>
<template>
  <div class="grid gap-4 p-qk-surface">
    <div class="flex flex-wrap items-center justify-between gap-3">
      <QuireSegmented :model-value="mode" :items="items" :label="text.modes" size="sm" @update:model-value="changeMode" />
      <div class="flex gap-2">
        <QuireButton size="sm" variant="ghost" :disabled="mode === 'locked'" @click="editor?.undo()">{{ text.undo }}</QuireButton>
        <QuireButton size="sm" variant="ghost" @click="reset">{{ text.reset }}</QuireButton>
        <QuireButton size="sm" variant="outline" @click="readMarkdown">{{ text.readMarkdown }}</QuireButton>
      </div>
    </div>
    <div ref="parent" class="overflow-hidden rounded-qk-control border border-qk-border-subtle" />
    <VueNodeViewHost :host="host" />
    <QuireProseSource v-if="markdown !== undefined" :code="markdown" language="markdown" filename="document.md" :max-height="260" />
  </div>
</template>

```

### shared/appearance.ts

```ts
import { Extension } from "@quirekit/msfe-core";
import { EditorView } from "@quirekit/cm-view";
import { Prec } from "@quirekit/cm-state";
import { HighlightStyle, syntaxHighlighting } from "@quirekit/cm-language";
import { tags } from "@lezer/highlight";
import { themeVariableReferences as token } from "@quirekit/ui";

/** Consumer-owned theme; source semantics stay in the installed feature packages. */
export const Appearance = Extension.create({ name: "documentationAppearance",
  /** @returns Scoped typography, surfaces and syntax colors from the host design system. */
  addCodeMirrorExtensions() {
    return Prec.high([
      EditorView.theme({
        "&": { ...token.typography.body, color: token.color.text.primary, backgroundColor: token.color.surface.page, minHeight: "180px" },
        ".cm-scroller": { fontFamily: "inherit", lineHeight: "1.8" },
        ".cm-content": { padding: token.spacing.surface, caretColor: token.color.text.primary },
        ".cm-line": { padding: "0" },
        ".cm-gutters": { color: token.color.text.tertiary, backgroundColor: token.color.surface.subtle, border: "none" },
        ".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "transparent" },
        ".cm-selectionBackground, &.cm-focused .cm-selectionBackground": { backgroundColor: token.color.selection.document },
        ".cm-cursor": { borderLeftColor: token.color.text.primary },
        ".msfe-h1": { ...token.typography.pageTitle },
        ".msfe-h2, .msfe-h3": { ...token.typography.sectionTitle },
        ".msfe-heading": { paddingBlock: token.spacing.inline },
        ".msfe-link": { color: token.color.text.accent },
        ".msfe-code-line, .msfe-inline-code": { ...token.typography.code },
        ".consumer-status": { verticalAlign: "middle" },
        "&.cm-focused": { outline: "none" },
      }),
      syntaxHighlighting(HighlightStyle.define([
        { tag: tags.heading, color: token.color.text.primary, fontWeight: "600" },
        { tag: tags.strong, fontWeight: "700" },
        { tag: tags.emphasis, fontStyle: "italic" },
        { tag: [tags.link, tags.url], color: token.color.text.accent },
        { tag: tags.processingInstruction, color: token.color.text.tertiary },
        { tag: tags.monospace, color: token.color.text.secondary },
      ])),
    ]);
  },
});

```

### shared/messages.ts

```ts
const chinese = {
  editor: "Markdown 示例编辑器", modes: "编辑器模式", source: "源码", live: "实时预览", locked: "只读",
  undo: "撤销", redo: "重做", reset: "重置", readMarkdown: "读取 Markdown", readingPreview: "阅读预览", insertStatus: "插入状态", done: "已完成", todo: "待处理",
  nativeEditor: "原生 DOM 示例编辑器", statusEditor: "自定义状态编辑器",
  initial: "## 把想法写下来\n\n在这里编辑 **Markdown**，也可以切换到源码。\n\n- [x] 保留 Markdown 原文\n- [ ] 试试修改这一行\n",
  nativeInitial: "## 原生 DOM 接入\n\n这个实例直接使用 `createMsfeEditor()`。\n",
  statusInitial: "## 自定义状态\n\n设计评审：[[todo]]\n\n点击状态按钮，然后读取 Markdown 或撤销。\n",
};
const english: { [Key in keyof typeof chinese]: typeof chinese[Key] } = {
  editor: "Markdown example editor", modes: "Editor mode", source: "Source", live: "Live", locked: "Locked",
  undo: "Undo", redo: "Redo", reset: "Reset", readMarkdown: "Read Markdown", readingPreview: "Reading preview", insertStatus: "Insert status", done: "Done", todo: "Pending",
  nativeEditor: "Native DOM example editor", statusEditor: "Custom status editor",
  initial: "## Write your ideas\n\nEdit **Markdown** here, or switch to source.\n\n- [x] Preserve the Markdown source\n- [ ] Try editing this line\n",
  nativeInitial: "## Native DOM integration\n\nThis instance uses `createMsfeEditor()` directly.\n",
  statusInitial: "## Custom status\n\nDesign review: [[todo]]\n\nClick the status, then read the Markdown or undo.\n",
};
export const exampleMessages = { "zh-CN": chinese, en: english };

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

## Customization entry points

| What to customize | Interface |
| --- | --- |
| Fonts, colors, spacing, and syntax highlighting | CodeMirror `EditorView.theme()` and `HighlightStyle`, installed through extensions |
| Source-bound components for images, tasks, and business nodes | `addNodeView()` and a framework renderer |
| Search panels, container folding, and preview actions | The feature's `ComponentViewRenderer` configuration |
| External toolbars, menus, and dialogs | Host components calling public commands, state queries, and guarded actions |

`theme: 'light' | 'dark'` and `setTheme()` control the built-in color scheme. Supply complete visual themes through extensions and CSS.

## Use QuireKit components

Configure the QuireKit UnoCSS preset, theme, and icon providers, then import buttons and other components from `@quirekit/ui/vue`. Use `themeVariableReferences` from `@quirekit/ui` to connect theme variables to the editor and keep its colors consistent with the page.

```ts
import { defineQuireKitUnoConfig } from '@quirekit/unocss-preset'

export default defineQuireKitUnoConfig()
```

## Document input ownership

CodeMirror continues to manage document input, selections, and the caret. Component forms can update explicit source fields through a field codec. Arbitrary editable component subtrees are not currently supported.

## A reusable theme module

~~~ts
import { Extension } from '@quirekit/msfe-core'
import { EditorView } from '@quirekit/cm-view'
import { themeVariableReferences as token } from '@quirekit/ui'

export const ProductTheme = Extension.create({
  name: 'productTheme',
  addCodeMirrorExtensions() {
    return EditorView.theme({
      '&': { color: token.color.text.primary, backgroundColor: token.color.surface.page },
      '.cm-content': { padding: token.spacing.surface },
      '.msfe-inline-code': { ...token.typography.code, backgroundColor: token.color.surface.subtle },
      '.msfe-link': { color: token.color.text.accent },
    })
  },
})
~~~

Install it through extensions. EditorView themes scope rules to the editor instead of globally styling pre, table or button. Use CodeMirror precedence when needed instead of accumulating global important rules.

## Customization map

| Visual object | Entry | Boundary |
| --- | --- | --- |
| Content, lines, selection and cursor | CodeMirror theme selectors | Retain input DOM ownership |
| Semantic lines and marks | MSFE heading, link, code and container classes | Preserve source reveal and native selection |
| Code tokens | HighlightStyle and syntaxHighlighting | Install the corresponding grammar |
| Business nodes | Host NodeView components | Write fields through codecs |
| Preview and disclosure controls | Host ComponentViews | Do not own editable body content |
| Documentation prose | QuireKit QuireProse | Separate consumer from editor-internal themes |

Regression-test class dependencies on upgrades rather than relying on private DOM ancestry. Broad resets such as `.cm-content * { all: unset }` can break controls and accessibility.

## Themes and fonts

Use QuireKit Provider CSS variables in the editor. `editor.setTheme()` updates MSFE presentation settings but does not automatically recolor external chart engines. Those views must update their own visual parameters.

Selection and highlight can use fixed colors instead of following the theme's primary color: override
`--qk-color-selection-document` (document selection, preferably translucent) and `--qk-prose-mark-background`
(`==highlight==`, otherwise mixed from the primary color and page surface). Use a selector at the same level as the
theme variables, such as `html[data-qk-theme][data-qk-scheme] { --qk-prose-mark-background: #fff1a1; }`, and set
a second pair for the dark scheme.

Fonts, line height and asynchronous resources affect geometry. Keep component geometry predictable and request measurement when it changes. Do not force every source line to a fixed height to hide overflowing previews.

## Integration order

Configure Provider, UnoCSS and tokens, then replace node and generic components, then add semantic themes. Keep the framework Host inside the Provider. Examples use QuireKit controls; feature extensions choose components and forward state/actions.

See [container styles](/en/extensions/containers), [code toolbars](/en/extensions/code-blocks), [charts](/en/extensions/diagrams) and [search panels](/en/customization/search).
