An accessible spreadsheet web component built with Lit 3.0. Implements the WAI-ARIA grid pattern with virtual rendering, a formula engine, undo/redo, and clipboard support.
- Full keyboard navigation (arrows, Tab, Enter, Escape, Ctrl+A)
- Copy/cut/paste with TSV clipboard format
- Undo/redo with Ctrl+Z / Ctrl+Shift+Z
- Formula engine with 20+ built-in functions and custom function support
- Arrow key cell reference insertion during formula editing
- Virtual rendering for large datasets (1000+ rows)
- Dual-mode formula bar (raw / formatted)
- Read-only mode
- CSS custom properties for theming
- Shadow DOM encapsulation
- Screen reader support with ARIA live regions
npm install y11n-spreadsheetLit is a peer dependency:
npm install lit<script type="module">
import 'y11n-spreadsheet';
</script>
<y11n-spreadsheet rows="50" cols="10"></y11n-spreadsheet>import 'y11n-spreadsheet';
import { cellKey } from 'y11n-spreadsheet';
const sheet = document.querySelector('y11n-spreadsheet');
const data = new Map();
data.set(cellKey(0, 0), { rawValue: 'Name', displayValue: 'Name', type: 'text' });
data.set(cellKey(0, 1), { rawValue: 'Score', displayValue: 'Score', type: 'text' });
data.set(cellKey(1, 0), { rawValue: 'Alice', displayValue: 'Alice', type: 'text' });
data.set(cellKey(1, 1), { rawValue: '95', displayValue: '95', type: 'number' });
data.set(cellKey(2, 1), { rawValue: '=SUM(B1:B2)', displayValue: '95', type: 'number' });
sheet.setData(data);| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
rows |
rows |
number |
100 |
Number of rows |
cols |
cols |
number |
26 |
Number of columns |
data |
- | GridData |
new Map() |
Sparse cell data (Map<string, CellData>) |
readOnly |
read-only |
boolean |
false |
Disables editing |
functions |
- | Record<string, FormulaFunction> |
{} |
Custom formula functions |
sheet.getData() // Returns a copy of the grid data
sheet.setData(data) // Replaces grid data (clears undo/redo)
sheet.registerFunction('DOUBLE', (ctx, val) => Number(val) * 2)// Single cell edited
sheet.addEventListener('cell-change', (e) => {
console.log(e.detail.cellId, e.detail.value, e.detail.oldValue);
});
// Selection moved
sheet.addEventListener('selection-change', (e) => {
console.log(e.detail.range); // { start: { row, col }, end: { row, col } }
});
// Bulk operation (paste, cut, clear, undo, redo)
sheet.addEventListener('data-change', (e) => {
console.log(e.detail.updates, e.detail.source, e.detail.operation);
});Formulas start with =. Supported syntax:
Operators: +, -, *, /, & (concat), =, <>, <, >, <=, >=
Cell references: A1, B2:D10 (ranges)
Built-in functions:
| Category | Functions |
|---|---|
| Aggregates | SUM, AVERAGE, MIN, MAX, COUNT, COUNTA |
| Conditional | IF(condition, trueVal, falseVal) |
| Math | ABS, ROUND |
| String | CONCAT, UPPER, LOWER, LEN, TRIM |
Custom functions:
sheet.registerFunction('TAX', (ctx, amount, rate) => {
return Number(amount) * Number(rate);
});
// Use in cells: =TAX(B1, 0.2)The ctx argument provides getCellValue(ref) and getRangeValues(start, end) for reading other cells.
Style the component using CSS custom properties:
y11n-spreadsheet {
--ls-font-family: 'Inter', system-ui;
--ls-font-size: 14px;
--ls-cell-width: 120px;
--ls-cell-height: 32px;
--ls-header-bg: #f8f9fa;
--ls-border-color: #dee2e6;
--ls-selection-border: 2px solid #0d6efd;
--ls-selection-bg: rgba(13, 110, 253, 0.08);
--ls-text-color: #212529;
}All CSS custom properties
| Property | Default | Description |
|---|---|---|
--ls-font-family |
system-ui |
Font family |
--ls-font-size |
13px |
Base font size |
--ls-text-color |
#333 |
Cell text color |
--ls-cell-width |
100px |
Column width |
--ls-cell-height |
28px |
Row height |
--ls-header-bg |
#f3f3f3 |
Header background |
--ls-border-color |
#e0e0e0 |
Cell border color |
--ls-selection-border |
2px solid #1a73e8 |
Selection outline |
--ls-selection-bg |
rgba(26, 115, 232, 0.1) |
Selection fill |
--ls-focus-ring |
2px solid #1a73e8 |
Focus indicator |
--ls-editor-bg |
#fff |
Inline editor background |
--ls-editor-shadow |
0 2px 6px rgba(0,0,0,0.2) |
Editor drop shadow |
--ls-formula-bar-bg |
#f8fafc |
Formula bar background |
--ls-formula-ref-bg |
#fff |
Formula bar input background |
--ls-formula-mode-active-bg |
#dbeafe |
Active mode toggle background |
--ls-ref-highlight-border |
#1a73e8 |
Reference highlight border |
--ls-ref-highlight-bg |
rgba(26, 115, 232, 0.15) |
Reference highlight fill |
The inline editor is exposed as a CSS part:
y11n-spreadsheet::part(editor) {
font-weight: bold;
}| Key | Action |
|---|---|
| Arrow keys | Move active cell |
| Shift + Arrow | Extend selection |
| Tab / Shift+Tab | Move right / left |
| Enter | Start editing / commit edit |
| Escape | Cancel edit / clear selection |
| Delete, Backspace | Clear selected cells |
| Ctrl+A | Select all |
| Ctrl+C / X / V | Copy / Cut / Paste |
| Ctrl+Z | Undo |
| Ctrl+Shift+Z | Redo |
| Any printable character | Start editing with that character |
During formula editing, arrow keys insert cell references instead of navigating.
All types are exported for TypeScript consumers:
import type {
CellData, // { rawValue, displayValue, type, style? }
CellCoord, // { row, col }
GridData, // Map<string, CellData>
SelectionRange, // { start: CellCoord, end: CellCoord }
FormulaContext, // { getCellValue, getRangeValues }
FormulaFunction, // (ctx: FormulaContext, ...args: unknown[]) => unknown
CellChangeDetail,
SelectionChangeDetail,
DataChangeDetail,
} from 'y11n-spreadsheet';Utility functions for coordinate conversion:
import { cellKey, parseKey, colToLetter, letterToCol, refToCoord, coordToRef } from 'y11n-spreadsheet';
cellKey(0, 0) // "0:0"
parseKey("0:0") // { row: 0, col: 0 }
colToLetter(0) // "A"
letterToCol("AA") // 26
refToCoord("B3") // { row: 2, col: 1 }
coordToRef({row:0, col:0}) // "A1"The package also exports internal modules if you need them standalone:
import { FormulaEngine } from 'y11n-spreadsheet';
import { SelectionManager } from 'y11n-spreadsheet';
import { ClipboardManager } from 'y11n-spreadsheet';
import { Y11nFormulaBar } from 'y11n-spreadsheet';- Node.js 22+
- npm
git clone <repo-url>
cd accessible-spreadsheet-component
npm install
npx playwright install # for E2E testsnpm run dev # Vite dev server at http://localhost:5173
npm run storybook # Storybook at http://localhost:6006The index.html at the project root has a test harness. Storybook has detailed interactive examples covering all features.
npm test # Unit tests (Vitest)
npm run test:watch # Unit tests in watch mode
npm run test:e2e # E2E tests headless (Playwright)
npm run test:e2e:headed # E2E tests in visible browser
npm run test:e2e:ui # E2E tests with Playwright UI
npm run typecheck # TypeScript type checkingsrc/
index.ts # Barrel exports
types.ts # Types & coordinate utilities
y11n-spreadsheet.ts # Main component
components/y11n-formula-bar.ts # Formula bar sub-component
controllers/
selection-manager.ts # Selection state (Lit ReactiveController)
clipboard-manager.ts # Clipboard operations (TSV format)
engine/
formula-engine.ts # Recursive descent formula parser
__tests__/ # Unit tests colocated with source
e2e/ # Playwright E2E tests
stories/ # Storybook stories
npm run build # Outputs ES module to dist/The build externalizes lit as a peer dependency and produces dist/index.js with TypeScript declarations and source maps.
MIT