| name | creating-docs-examples |
| path | docs/** |
| description | Use when creating code examples for documentation pages - JavaScript, TypeScript, React, Angular, and Vue variants with proper imports, registration, and license key |
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 / .tsx file first for JavaScript and React examples. From docs/, generate the JS variant with: npm run docs:code-examples:generate-js -- <path-to-ts-file> (path relative to docs/). Never hand-edit generated JS files. For Vue, write TypeScript inside the .vue file 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 #example div:
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 (not app.module.ts) with ApplicationConfig, provideZoneChangeDetection({ eventCoalescing: true }), and global HOT_GLOBAL_CONFIG for the license key.
- Do not add
licenseKey to individual <hot-table> bindings -- it is set globally in app.config.ts.
- Template control flow: use
@if / @for (x of list; track x.id) -- never *ngIf / *ngFor.
- Name the component class
AppComponent in 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
styleUrls in standalone components. CSS is injected globally by the example-runner via the --css slot. If you need component-scoped styles, use inline styles: ['...'].
- ❌ Never use
templateUrl. Always define the component's template inline with template: \...`. The angular/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 throws NG0202.
- ❌ Never bind Handsontable hooks in the template (
(afterInit)="handler()"). Put hook functions inside gridSettings instead.
- ❌ 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:
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 = { ... };
}
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: [
({ : }),
{ : , : { : } },
],
};
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.vue file per example, with <script setup lang="ts">.
- ❌ Do not: use plain
<script setup> without lang="ts".
- ❌ Do not: split logic into
exampleN.js + exampleN.html (legacy pattern). When you touch an old split example, migrate it to a .vue SFC.
- ❌ Do not: use the Options API (
defineComponent with data(), 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 with GridSettings from handsontable/settings. Add local type aliases for row or domain data when the example uses object rows.
- Call
registerAllModules() once at the top level of <script setup> (not inside onMounted).
- Import
HotTable and HotColumn from @handsontable/vue3. Register them by using them in <template> (no global app.component() registration).
- Put
licenseKey: 'non-commercial-and-evaluation' inside the settings object passed to HotTable.
- The root
<div> in <template> must use an id that matches the example container in the guide (#example1 in ::: example #example1 :vue3).
- Prefer a single
:settings object 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() from vue for reactive state that the template or handlers update (hotSettings, toggles, selected values). Use a plain const for hotSettings when reactive deep updates would trigger unwanted updateSettings() calls (for example, when only a status label changes beside the grid).
- Always use
useTemplateRef('refName') for template refs bound via ref="..." in <template>. Never use ref() 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.
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 :settings on each HotColumn.
- Optional
<style scoped> is allowed for example-only UI (buttons, status text). Example-runner CSS from the guide's --css slot 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