Embedding Guide
formulon-cell is a reference UI library, so its pieces are intentionally reusable. The bundled playground mounts the package with presets.full(), but hosts that adopt ideas from it should usually start with a smaller preset and add only the pieces they need.
Do not treat the default chrome as a complete Excel-compatible application shell. It is useful for integration testing and examples; production products should decide their own feature coverage, UX, and quality bar.
Glossary: preset vs extension
A preset is a curated list of features. An extension is a single composable feature factory. presets.minimal() / presets.standard() / presets.full() each return a plain FeatureFlags object — not an array of extensions; you can build the same set of flags yourself.
Three host shapes
- Drop-in spreadsheet. Use a preset, accept the default chrome, customize via i18n and themes.
- Mixed chrome. Use
presets.minimal()and add only the dialogs / toolbars you need as extensions. - Headless surface. Mount the canvas without chrome, drive it from your application's own toolbar via command helpers.
Drop-in mount
import { Spreadsheet, WorkbookHandle, presets } from '@libraz/formulon-cell'
import '@libraz/formulon-cell/styles.css'
const host = document.getElementById('sheet')!
const workbook = await WorkbookHandle.createDefault()
const instance = await Spreadsheet.mount(host, {
workbook,
features: presets.full(),
locale: 'en',
theme: 'paper'
})Selective extensions
MountOptions has two independent knobs for feature composition:
features: FeatureFlags— a plain object of booleans that turns a built-in on or off (e.g.{ findReplace: false }).presets.minimal()/presets.standard()/presets.full()each return one of these objects — never an array, so don't spread one into a list.extensions: ExtensionInput[]— an array of extension factories (zero-argument functions returning anExtension) that mount alongside — or, once you've disabled the matching built-in, in place of — the default chrome.
Replaceable factories live in the same export — verified against extensions/index.ts, called with no arguments:
import {
Spreadsheet,
presets,
findReplace,
formatDialog,
namedRangeDialog,
hyperlinkDialog,
pivotTableDialog,
validationList,
hoverComment,
viewToolbar,
quickAnalysis
} from '@libraz/formulon-cell'
const instance = await Spreadsheet.mount(host, {
workbook,
// Disable the built-ins you want to replace or drop...
features: { ...presets.minimal(), findReplace: false },
// ...and mount your own selection through `extensions` instead.
extensions: [findReplace(), formatDialog(), namedRangeDialog()]
})autocomplete has no matching extension factory — it is a features flag only (features: { autocomplete: false } turns it off; there is nothing to substitute it with).
The order in the extensions array is the activation order. Most extensions are independent, but a few cooperate (e.g. pasteSpecial integrates with the clipboard command helper). When in doubt, check the order allBuiltIns mounts internally.
Headless mount
const headless = await Spreadsheet.mount(host, {
workbook,
features: presets.minimal(),
locale: 'en'
})
// Read what the engine knows
const state = headless.store.getState()
const active = state.selection.activeFrom there, drive selection and edits from your own toolbar using command helpers.
Command helpers
The package exports flat, engine-backed command functions — the same ones the chrome and extensions call internally — that operate on a State snapshot (instance.store.getState()), not on the store object itself. There are no clipboardCommands / formattingCommands grouped namespaces; import the functions you need directly:
import { copy, cut, pasteTSV, applyPasteSpecial, toggleBold, setNumFmt, addConditionalRule, listComments } from '@libraz/formulon-cell'
const state = instance.store.getState()
copy(state) // clipboard
toggleBold(state, instance.store) // formatting (some helpers also take the store)
setNumFmt(state, instance.store, '#,##0.00')
listComments(state) // read-only helpers take just the state
addConditionalRule(instance.store, { // a few rule/history helpers take the store directly
kind: 'cell-value',
range: { sheet: 0, r0: 1, c0: 1, r1: 10, c1: 1 },
op: '>',
a: 100,
apply: { fill: '#ffe4e4' }
})Check @libraz/formulon-cell's index.ts re-exports for the full list — clipboard (copy/cut/pasteTSV/applyPasteSpecial), formatting (toggleBold/setNumFmt/setFont/…), named ranges (listDefinedNames/upsertDefinedName/…), comments (setComment/listComments/…), hyperlinks (setHyperlink/listHyperlinks/…), conditional formatting (addConditionalRule/listConditionalRules/…), and selection aggregates (aggregateSelection/visibleStatusAggregates) for status bars.
Same code path as the built-in chrome
Whatever the built-in toolbars do, the command helpers do the same way. That means features stay in sync — a host-built toolbar gets the same undo entries, the same recalc behavior, and the same event emissions.
Ribbon toolbar
The quickest way to add the ribbon is the toolbar option on Spreadsheet.mount. It builds the ribbon inside the host in a single call — no separate toolbar host to wire — and the ribbon shares the grid's data-fc-theme, so one setTheme() re-themes both surfaces:
const instance = await Spreadsheet.mount(host, {
workbook,
features: presets.full(),
toolbar: true, // or a MountToolbarOptions object
})
// instance.toolbar is the ToolbarInstance (null when the toolbar is not requested)Pass a MountToolbarOptions object instead of true to add backstage content (createBackstageView), hooks, submenu factories, a ribbon-tab profile, or lifecycle callbacks (onTabChange, …) while keeping the single call — the fields you set are merged over the built-in defaults. instance.dispose() tears the toolbar down with the rest of the instance.
In React / Vue, the framework packages' ready-made SpreadsheetToolbar component wraps the same ribbon — a thin adapter over core that ships the ribbon DOM, menu factories, activation model, and dropdown dispatcher for you:
// React
import { Spreadsheet, SpreadsheetToolbar } from '@libraz/formulon-cell-react'
import '@libraz/formulon-cell-react/toolbar.css'
export function Sheet() {
const [instance, setInstance] = useState<SpreadsheetInstance | null>(null)
const [activeTab, setActiveTab] = useState<RibbonTab>('home')
return (
<>
<SpreadsheetToolbar
instance={instance}
activeTab={activeTab}
locale="en"
onTabChange={setActiveTab}
/>
<Spreadsheet locale="en" onReady={setInstance} />
</>
)
}<!-- Vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { type RibbonTab, type SpreadsheetInstance } from '@libraz/formulon-cell'
import { Spreadsheet } from '@libraz/formulon-cell-vue'
import SpreadsheetToolbar from '@libraz/formulon-cell-vue/toolbar.vue'
import '@libraz/formulon-cell-vue/toolbar.css'
const instance = ref<SpreadsheetInstance | null>(null)
const activeTab = ref<RibbonTab>('home')
</script>
<template>
<SpreadsheetToolbar :instance="instance" :active-tab="activeTab" locale="en" @tab-change="(tab) => (activeTab = tab)" />
<Spreadsheet locale="en" @ready="(inst) => (instance = inst)" />
</template>Use the dropdownActions prop to override individual ribbon dropdown handlers (script/add-in actions, protect dialogs, etc.) without forking the ribbon:
<SpreadsheetToolbar
instance={instance}
activeTab={activeTab}
locale="en"
onTabChange={setActiveTab}
dropdownActions={{ applyProtectAction: openProtectDialog }}
/>Both adapters mirror the same prop shape and delegate to core, so framework wrappers and host-built toolbars use the same command path.
Separate toolbar host (advanced)
When the single-call toolbar option isn't enough — a host without React or Vue that needs the ribbon in a fully separate DOM host (outside .fc-host), or lower-level control than SpreadsheetToolbar exposes — call Spreadsheet.mountToolbar(host, instance, opts) from core directly. Its theme option and ToolbarInstance.setTheme() speak the same paper / ink / contrast vocabulary as the grid:
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
import { Spreadsheet as CoreSpreadsheet, type RibbonTab, type SpreadsheetInstance, type ToolbarInstance } from '@libraz/formulon-cell'
import { Spreadsheet } from '@libraz/formulon-cell-vue'
const instance = ref<SpreadsheetInstance | null>(null)
const activeTab = ref<RibbonTab>('home')
const toolbarHost = ref<HTMLDivElement | null>(null)
let toolbar: ToolbarInstance | null = null
watch(instance, async (next) => {
await nextTick()
if (!next || !toolbarHost.value) return
toolbar?.dispose()
toolbar = CoreSpreadsheet.mountToolbar(toolbarHost.value, next, {
lang: 'en',
activeTab: activeTab.value,
onTabChange: (tab) => (activeTab.value = tab)
})
})
</script>
<template>
<div ref="toolbarHost"></div>
<Spreadsheet locale="en" @ready="(inst) => (instance = inst)" />
</template>For host audits or custom chrome, import the shared manifests from core (ribbonActivationEntries, ribbonSurfaceCommandIds, DYNAMIC_RIBBON_DROPDOWN_HANDLER_ATTRS) instead of reconstructing ribbon command sets.
Lifecycle hooks
Mount returns a SpreadsheetInstance with dispose(). Spreadsheet.mount() rejects if it can't produce an instance (most commonly when the WASM engine can't start — see Stub engine), so wrap it in try/catch or pass MountOptions.onError:
useEffect(() => {
let instance: SpreadsheetInstance | undefined
;(async () => {
try {
instance = await Spreadsheet.mount(host, {
workbook,
features: presets.minimal(),
onError: (err) => showConfigurationError(err),
})
} catch (err) {
// onError already ran; this catch guards callers that omit it.
showConfigurationError(err)
}
})()
return () => instance?.dispose()
}, [])dispose() detaches event listeners, unmounts DOM, and releases the engine reference held by the chrome. The WorkbookHandle itself is owned by the caller; release it with wb.dispose() when the application is done. The React and Vue adapters expose the same failure as an onError prop / error event plus an errorFallback prop for a framework-native fallback UI.
Stub-engine detection
preferStub: true is the explicit, opt-in way to get the in-memory stub engine — for tests and demos only, never as an automatic production fallback:
import { WorkbookHandle } from '@libraz/formulon-cell'
const wb = await WorkbookHandle.createDefault({ preferStub: true })
if (wb.isStub) {
showBanner('Running on the stub engine — only a small formula subset evaluates, and save is unavailable.')
}wb.isStub (and the module-level isUsingStub()) reflect whether the stub engine is in use. It does not change at runtime once the workbook is created. Without preferStub, a missing SharedArrayBuffer makes createDefault() reject instead — see Bundler setup for the COOP/COEP requirements.
React adapter
import { Spreadsheet, presets } from '@libraz/formulon-cell-react'
export function Sheet() {
return (
<Spreadsheet
features={presets.standard()}
locale="en"
theme="paper"
onSelectionChange={(event) => console.log(event.active)}
/>
)
}The React adapter mounts and disposes for you and forwards events as props. Pass onError and/or errorFallback to handle a rejected mount (see Lifecycle hooks) instead of letting it surface as an unhandled rejection. For more control, drop down to the vanilla package.
@libraz/formulon-cell-react also exports hooks for reading instance state without wiring up your own store subscription:
| Hook | Description |
|---|---|
useSelection(instance) | Subscribe to the active selection |
useSpreadsheet(instance, selector, fallback) | Subscribe to a selector over the store's State, with an SSR-safe fallback |
useI18n(instance) | Read the current locale + strings, reactive to runtime setLocale/extend/register |
useSpreadsheetEvent(instance, event, handler) | Subscribe to a SpreadsheetInstance lifecycle event (cellChange, selectionChange, …) |
Vue adapter
<script setup lang="ts">
import { Spreadsheet, presets } from '@libraz/formulon-cell-vue'
</script>
<template>
<Spreadsheet
:features="presets.standard()"
locale="en"
theme="paper"
@selection-change="(event) => console.log(event.active)"
@error="(err) => showConfigurationError(err)"
/>
</template>@libraz/formulon-cell-vue exports the same set of composables as the React adapter, for reading instance state without wiring up your own store subscription:
| Composable | Description |
|---|---|
useSelection(instance) | Subscribe to the active selection |
useSpreadsheet(instance, selector, fallback) | Subscribe to a selector over the store's State, with an SSR-safe fallback |
useI18n(instance) | Read the current locale + strings, reactive to runtime setLocale/extend/register |
useSpreadsheetEvent(instance, event, handler) | Subscribe to a SpreadsheetInstance lifecycle event (cellChange, selectionChange, …) |
Read next
- i18n — locale registration and overrides.
- API surface — events, store, command helpers.
- Bundler setup — what the host must serve.