# Document structure

Organize content with Heading, Paragraph and List.

These generic structures apply to product-neutral Classic and Block. Classic Word uses its own paragraphs, headings and numbering; Word tables and numbering need separate Block adapters. See [editor compatibility](/en/plugins/compatibility).

Headings and lists are document nodes, not temporary DOM styling. This page uses the standard structure extensions in StarterKit (MIT). For separate installation, use `@quirekit/extension-heading`, `@quirekit/extension-paragraph` and `@quirekit/extension-list`, retaining all schema-required nodes.

## Configuration and commands

```ts
StarterKit.configure({ heading: { levels: [1, 2, 3] } })
editor.chain().focus().toggleHeading({ level: 2 }).run()
editor.chain().focus().toggleBulletList().run()
```

## Live effect

Place the caret in a paragraph, then choose Heading 2 or Bullet list. Type, press Enter to continue the structure, then undo. JSON downloads show real node changes.

### plugins/structure.vue

```vue
<script setup lang="ts">
import CoreEditor from "../core/editor.vue";
defineProps<{ /** Page language. */ locale: "zh-CN" | "en" }>();
</script>
<template><CoreEditor :locale="locale" tools="structure" /></template>

```

### core/editor.vue

```vue
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import type { Editor } from "@quirekit/core";
import { QuireButton } from "@quirekit/ui/vue";
import { createExampleEditor } from "./create";
import { downloadJson } from "../shared/download";
import "@quirekit/pm-view/style/prosemirror.css";

const props = withDefaults(defineProps<{
  /** Initial copy and control language. */ locale?: "zh-CN" | "en";
  /** Visible controls for the capability being demonstrated. */ tools?: "formatting" | "structure";
}>(), { locale: "zh-CN" });
const host = ref<HTMLElement>();
const instance = shallowRef<Editor>();
const copy = computed(() => props.locale === "en"
  ? { bold: "Bold", undo: "Undo", save: "Download JSON", label: "Editable document" }
  : { bold: "加粗", undo: "撤销", save: "下载 JSON", label: "可编辑文档" });
onMounted(() => { if (host.value) instance.value = createExampleEditor(host.value, props.locale); });
onBeforeUnmount(() => instance.value?.destroy());
/** Downloads the actual editor state instead of a second copy maintained by the UI. */
function download(): void { if (instance.value) downloadJson(instance.value.getJSON(), "quirekit-document.json"); }
</script>
<template>
  <div class="core-example">
    <div class="core-example-tools">
      <QuireButton variant="outline" @click="instance?.chain().focus().toggleBold().run()">{{ copy.bold }}</QuireButton>
      <template v-if="tools === 'formatting'">
        <QuireButton variant="outline" @click="instance?.chain().focus().toggleItalic().run()">{{ locale === 'en' ? 'Italic' : '斜体' }}</QuireButton>
        <QuireButton variant="outline" @click="instance?.chain().focus().toggleUnderline().run()">{{ locale === 'en' ? 'Underline' : '下划线' }}</QuireButton>
      </template>
      <template v-if="tools === 'structure'">
        <QuireButton variant="outline" @click="instance?.chain().focus().toggleHeading({ level: 2 }).run()">{{ locale === 'en' ? 'Heading 2' : '二级标题' }}</QuireButton>
        <QuireButton variant="outline" @click="instance?.chain().focus().toggleBulletList().run()">{{ locale === 'en' ? 'Bullet list' : '项目列表' }}</QuireButton>
      </template>
      <QuireButton variant="outline" @click="instance?.chain().focus().undo().run()">{{ copy.undo }}</QuireButton>
      <QuireButton variant="outline" @click="download">{{ copy.save }}</QuireButton>
    </div>
    <div ref="host" class="core-example-document" role="group" :aria-label="copy.label" />
  </div>
</template>
<style scoped>
.core-example { padding: 20px; background: var(--qk-color-surface-page); }
.core-example-tools { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
.core-example-document { border: 1px solid var(--qk-color-border-default); border-radius: 6px; padding: 20px; text-align: start; }
.core-example-document :deep(.ProseMirror) { min-height: 180px; line-height: 1.8; }
/* A writing surface expresses focus through its caret and selection, without a second frame. */
.core-example-document :deep(.ProseMirror:focus), .core-example-document :deep(.ProseMirror:focus-visible) { outline: none; box-shadow: none; }
.core-example-document :deep(h2) { font-size: 22px; margin: 0 0 12px; }
.core-example-document :deep(p) { margin: 8px 0; }
</style>

```

### core/create.ts

```ts
import { Editor } from "@quirekit/core";
import { StarterKit } from "@quirekit/starter-kit";
import { DomViewExtension } from "@quirekit/dom-view";
import { writingShortcuts } from "./shortcuts";

/**
 * Mounts a minimal editor with the Tiptap-style Core API and an explicit native DOM view.
 * @param element - Browser-owned mount target.
 * @param locale - Language for initial sample copy only.
 * @returns The caller-owned editor; destroy it when its host unmounts.
 */
export function createExampleEditor(element: HTMLElement, locale: "zh-CN" | "en"): Editor {
  return new Editor({
    element,
    mode: "dom",
    extensions: [StarterKit, DomViewExtension, writingShortcuts],
    content: { type: "doc", content: [
      { type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: locale === "en" ? "Start with your words." : "从你写下的文字开始。" }] },
      { type: "paragraph", content: [{ type: "text", text: locale === "en" ? "Select some text, make it bold, or add a new paragraph. This is a real QuireKit Core editor." : "选择一段文字，加粗，或另起一段。这里运行的是真实的 QuireKit Core 编辑器。" }] },
    ] },
  });
}

```

### core/shortcuts.ts

```ts
import { Extension } from "@quirekit/core";

/** A host-authored behavior extension using the same typed command registry as the toolbar. */
export const writingShortcuts = Extension.create({
  name: "writingShortcuts",
  /** @returns A keyboard binding that participates in the editor's existing history. */
  addKeyboardShortcuts() {
    return { "Mod-Shift-b": () => this.editor?.commands.toggleBold() ?? false };
  },
});

```

### shared/download.ts

```ts
/**
 * Downloads canonical JSON through a short-lived browser URL.
 * @param document - Serializable canonical content.
 * @param name - Download filename chosen by the host.
 */
export function downloadJson(document: unknown, name: string): void {
  const url = URL.createObjectURL(new Blob([JSON.stringify(document, null, 2)], { type: "application/json" }));
  const link = window.document.createElement("a");
  link.href = url;
  link.download = name;
  link.click();
  window.setTimeout(() => URL.revokeObjectURL(url), 1000);
}

```

Word numbering definitions, paragraph styles, pagination and OOXML round trips belong to the Word packages. Generic lists are not equivalent to those capabilities; see [Word integration](/en/plugins/word).

## Individual plugin references

[Heading](/en/plugins/reference/heading), [Paragraph](/en/plugins/reference/paragraph), [ListKit](/en/plugins/reference/list-kit). See [all plugins](/en/plugins) for other capabilities.
