Creating Documentation Code Examples
This skill covers how to write runnable code examples that are embedded in documentation guide pages.
File Structure
Each guide has framework-specific subdirectories for its examples:
docs/content/guides/category/feature/
feature.md # The guide page
javascript/ # JS examples
example1.js # Generated from TS - do not edit directly
example1.ts # Primary source - edit this first
react/ # React variants
example1.jsx # Generated from TSX
example1.tsx # Primary source
angular/ # Angular variants
example1.ts
example1.html # Template file
vue/ # Vue 3 variants
example1.vue # TypeScript SFC (`<script setup lang="ts">`)
Key Rules
- 25-60 lines per example. Keep examples focused and scannable.
- One concept per example. Use progressive numbering for complexity:
example1= basic setup,example2= a configuration variation,example3= advanced usage. - TypeScript is primary. Always write the
.ts/.tsxfile first for JavaScript and React examples. Fromdocs/, generate the JS variant with:npm run docs:code-examples:generate-js -- <path-to-ts-file>(path relative todocs/). Never hand-edit generated JS files. For Vue, write TypeScript inside the.vuefile with<script setup lang="ts">— there is no separate JS variant to generate. - Use realistic data. Prefer
createSpreadsheetData()or domain-appropriate sample data (product names, dates, currencies). Avoid trivial arrays like[1, 2, 3].
Required Elements in Every Example
Imports - use the base import and explicit registration:
import Handsontable from 'handsontable/base'; import { registerAllModules } from 'handsontable/registry'; registerAllModules();For examples that demonstrate tree-shaking, import individual plugins and cell types instead of
registerAllModules().License key - always include:
licenseKey: 'non-commercial-and-evaluation'Container element - target the conventional
#examplediv:const container = document.querySelector('#example');
Framework-Specific Patterns
React (TSX/JSX):
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
registerAllModules();
const App = () => {
return <HotTable data={data} licenseKey="non-commercial-and-evaluation" />;
};
Angular:
- All components must be standalone (
standalone: true,imports: [HotTableModule]). - Use
app.config.ts(notapp.module.ts) withApplicationConfig,provideZoneChangeDetection({ eventCoalescing: true }), and globalHOT_GLOBAL_CONFIGfor the license key. - Do not add
licenseKeyto individual<hot-table>bindings -- it is set globally inapp.config.ts. - Template control flow: use
@if/@for (x of list; track x.id)-- never*ngIf/*ngFor. - Name the component class
AppComponentin every example.
Critical Angular JIT restrictions — the docs site bootstraps Angular examples with JIT in the browser. JIT cannot load external files at runtime:
- ❌ Never use
styleUrlsin standalone components. CSS is injected globally by the example-runner via the--cssslot. If you need component-scoped styles, use inlinestyles: ['...']. - ❌ Never use
templateUrl. Always define the component's template inline withtemplate: \...`. Theangular/example1.html` file is the outer wrapper (selector tag) consumed by the example-runner -- it is not the component's template. - ❌ Never inject services via the constructor. Use
inject()instead. JIT mode lacks TypeScript decorator metadata, so constructor DI throwsNG0202. - ❌ Never bind Handsontable hooks in the template (
(afterInit)="handler()"). Put hook functions insidegridSettingsinstead. - ❌ Only import symbols you actually use. Unused imports (e.g.,
RowObject,ViewChild,NgFor) can cause module resolution errors.
The .ts file contains both app.component.ts and app.config.ts as separate /* file: ... */ sections within a single file:
/* file: app.component.ts */
import { Component } from '@angular/core';
import { GridSettings, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
standalone: true,
imports: [HotTableModule],
selector: 'example1-feature-name',
template: `
<div>
<hot-table [data]="data" [settings]="gridSettings"></hot-table>
</div>
`,
})
export class AppComponent {
readonly data = [...];
readonly gridSettings: GridSettings = { ... };
}
/* end-file */
/* file: app.config.ts */
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { registerAllModules } from 'handsontable/registry';
import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{ provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig },
],
};
/* end-file */
The angular/example1.html file is the outer wrapper (not the component template):
<div>
<example1-feature-name></example1-feature-name>
</div>
Edit on StackBlitz: When you use Edit on StackBlitz, docs/public/example-tabs.js merges each framework's companion example*.html into the generated app shell. parseDocsExampleHtmlForStackBlitz uses the browser DOMParser to collect style nodes for <head> and drop script nodes from the body fragment. mergeCompanionHtmlForStackBlitz wires that into the StackBlitz template. Examples with no HTML tab keep the previous default mount markup.
See skill angular-wrapper-dev for the full reference.
Vue 3:
Write every new or updated Vue example as a TypeScript Single-File Component (.vue) using the Composition API and <script setup lang="ts">. The docs example-runner loads vue/example*.vue modules and mounts them with createApp() (see docs/src/scripts/example-runner.ts).
- ✅ Do: one
exampleN.vuefile per example, with<script setup lang="ts">. - ❌ Do not: use plain
<script setup>withoutlang="ts". - ❌ Do not: split logic into
exampleN.js+exampleN.html(legacy pattern). When you touch an old split example, migrate it to a.vueSFC. - ❌ Do not: use the Options API (
defineComponentwithdata(),methods, etc.) in new examples.
SFC skeleton:
<script setup lang="ts">
import { ref } from 'vue';
import { HotTable } from '@handsontable/vue3';
import { registerAllModules } from 'handsontable/registry';
import type { GridSettings } from 'handsontable/settings';
registerAllModules();
const hotSettings = ref<GridSettings>({
data: [
['Acme Corp', 'Q1 2025', '$4.2M'],
['Vertex Industries', 'Q1 2025', '$18.7M'],
],
colHeaders: true,
height: 'auto',
licenseKey: 'non-commercial-and-evaluation',
});
</script>
<template>
<div id="example1">
<HotTable :settings="hotSettings" />
</div>
</template>
Vue-specific rules:
- Always use
<script setup lang="ts">. Type grid options withGridSettingsfromhandsontable/settings. Add localtypealiases for row or domain data when the example uses object rows. - Call
registerAllModules()once at the top level of<script setup>(not insideonMounted). - Import
HotTableandHotColumnfrom@handsontable/vue3. Register them by using them in<template>(no globalapp.component()registration). - Put
licenseKey: 'non-commercial-and-evaluation'inside the settings object passed toHotTable. - The root
<div>in<template>must use anidthat matches the example container in the guide (#example1in::: example #example1 :vue3). - Prefer a single
:settingsobject for grid options. Use individual props only when the guide text highlights a specific prop. - Put Handsontable hooks (
afterChange,beforeDataProviderFetch, etc.) inside the settings object, not as Vue event listeners on<HotTable>. - Use
ref()fromvuefor reactive state that the template or handlers update (hotSettings, toggles, selected values). Use a plainconstforhotSettingswhen reactive deep updates would trigger unwantedupdateSettings()calls (for example, when only a status label changes beside the grid). - Always use
useTemplateRef('refName')for template refs bound viaref="..."in<template>. Never useref()for template refs. - HotTable instance access:
import { useTemplateRef } from 'vue';
const hotRef = useTemplateRef<InstanceType<typeof HotTable>>('hotRef');
<HotTable ref="hotRef" :settings="hotSettings" />
Access: hotRef.value?.hotInstance.
- DOM element refs:
const dropdownRef = useTemplateRef<HTMLDivElement>('dropdownRef');
<div ref="dropdownRef" class="theme-dropdown">...</div>
Access: dropdownRef.value. The string passed to useTemplateRef(...) must match the template ref attribute exactly.
- For
HotColumn, nest it inside<HotTable>in<template>and pass column options via:settingson eachHotColumn. - Optional
<style scoped>is allowed for example-only UI (buttons, status text). Example-runner CSS from the guide's--cssslot still applies globally.
Presets on the ::: example directive select dependencies: :vue3 (default), :vue3-languages, :vue3-vuex. Match the preset to the feature the page demonstrates.
Embedding a Vue SFC (single tab, no --html / --js):
::: example #example1 :vue3
@[code](@/content/guides/category/feature/vue/example1.vue)
:::
See skill vue-wrapper-dev for wrapper behavior (HotTable, HotColumn, settings propagation).
Embedding in the Guide
After creating example files, embed them in the guide's .md file using the @[code] directive inside an ::: example container. See the writing-docs-pages skill for the full embedding syntax.
Checklist
- TypeScript source written and tested (
.ts/.tsx, or Vue.vuewithlang="ts"). - JS variant generated for JavaScript/React examples (not hand-written). Vue examples have no JS variant.
-
licenseKey: 'non-commercial-and-evaluation'present. - Imports use
handsontable/base+ registration pattern. - Example stays within 25-60 lines.
- One concept per example with realistic data.
- Vue examples use
.vueSFC with<script setup lang="ts">and Composition API (no split.js/.html, no Options API).