KQ-1735 · CELL/NOTES
Cell Notes
A spreadsheet whose notes live in their cells: every annotated cell carries a small corner, and opening a note grows its card out of that corner — one value on springs.glide sweeps a diagonal crease across the card's clip with a lip of paper along it, so the note unfolds from the cell — while closing folds it back on the exit ease. Replies stack inside the card on springs.snap, resolving folds the corner back flat on snap, and the active cell's outline travels cell to cell on snap. It is a real role="grid" with one roving cell: arrows, Home, End and Ctrl+Home/End move, Shift+F2 opens or starts the active cell's note, Escape closes it and returns focus, and the notes sidebar becomes a sheet below a 40rem container.
Fernworks · FY27 budget
4 notes · 3 open
Arrow keys move between cells. Shift with F2 opens the cell's note or starts one, Enter opens a note, Escape closes it.
B2 · no note · 3 open
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/cell-notes
Props
- cells
- CellNotesValue[][]
- default defaultCellNotesCells
- The sheet, row by row (strings, numbers or null); row 1 is the sheet's own heading row.
- columns
- CellNotesColumn[]
- default defaultCellNotesColumns
- Per column, A first: { width?, kind?: "text" | "money" | "number" | "percent" }.
- strongRows
- number[]
- default [1, 9]
- Row numbers (from 1) drawn bold, like headings and totals.
- notes / defaultNotes
- CellNote[]
- default — / defaultCellNotes
- Notes as { id, cell: "C4", author, at, text, replies: { id, author, at, text }[], resolved? }, controlled or initial.
- onNotesChange
- (notes) => void
- default —
- Fires with every note after an add, a reply or a resolve.
- active / defaultActive / onActiveChange
- string
- default — / "B2"
- The active cell as a reference, controlled or initial, and the callback from the click or key that moved it.
- open / defaultOpen / onOpenChange
- string | null
- default — / null
- The open note's id, controlled or initial, and the callback when a card opens or closes.
- onAdd
- (note) => void
- default —
- A note was added to a cell; it is already in the list.
- onReply
- (noteId, reply) => void
- default —
- A reply was posted on a note.
- onResolve
- (noteId, resolved) => void
- default —
- A note was resolved (true) or reopened (false).
- cornerStyle
- "fold" | "flag" | "dot"
- default "fold"
- A folded paper corner whose card unfolds with a lip of paper, a flat flag, or a dot (the card grows by clip alone).
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Row height (28/32/38px), cell type and card width.
- accent
- string
- default "var(--accent-bright)"
- Corners, the active outline, the card's top edge and Reply — any CSS colour, taken at a fixed lightness.
- locale
- string
- default "en-US"
- Locale for numbers and money.
- currency
- string
- default "USD"
- Currency for money columns.
- author
- string
- default "Noor Haddad"
- The name new notes and replies are posted under.
- now
- number | Date
- default defaultCellNotesNow
- The moment relative times count from, so server and browser render the same text.
- title
- string
- default "Fernworks · FY27 budget"
- The sheet's name and the surface's accessible name.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows a skeleton sheet and notes; error shows Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the sheet failed to load.
- height
- number
- default 540
- The surface's height in px; the grid and the notes scroll inside it.
- disabled
- boolean
- default false
- Read only: notes open and read; nothing is added, replied to or resolved.
- className
- string
- default —
- Classes for the surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| cells | CellNotesValue[][] | defaultCellNotesCells | The sheet, row by row (strings, numbers or null); row 1 is the sheet's own heading row. |
| columns | CellNotesColumn[] | defaultCellNotesColumns | Per column, A first: { width?, kind?: "text" | "money" | "number" | "percent" }. |
| strongRows | number[] | [1, 9] | Row numbers (from 1) drawn bold, like headings and totals. |
| notes / defaultNotes | CellNote[] | — / defaultCellNotes | Notes as { id, cell: "C4", author, at, text, replies: { id, author, at, text }[], resolved? }, controlled or initial. |
| onNotesChange | (notes) => void | — | Fires with every note after an add, a reply or a resolve. |
| active / defaultActive / onActiveChange | string | — / "B2" | The active cell as a reference, controlled or initial, and the callback from the click or key that moved it. |
| open / defaultOpen / onOpenChange | string | null | — / null | The open note's id, controlled or initial, and the callback when a card opens or closes. |
| onAdd | (note) => void | — | A note was added to a cell; it is already in the list. |
| onReply | (noteId, reply) => void | — | A reply was posted on a note. |
| onResolve | (noteId, resolved) => void | — | A note was resolved (true) or reopened (false). |
| cornerStyle | "fold" | "flag" | "dot" | "fold" | A folded paper corner whose card unfolds with a lip of paper, a flat flag, or a dot (the card grows by clip alone). |
| density | "compact" | "regular" | "roomy" | "regular" | Row height (28/32/38px), cell type and card width. |
| accent | string | "var(--accent-bright)" | Corners, the active outline, the card's top edge and Reply — any CSS colour, taken at a fixed lightness. |
| locale | string | "en-US" | Locale for numbers and money. |
| currency | string | "USD" | Currency for money columns. |
| author | string | "Noor Haddad" | The name new notes and replies are posted under. |
| now | number | Date | defaultCellNotesNow | The moment relative times count from, so server and browser render the same text. |
| title | string | "Fernworks · FY27 budget" | The sheet's name and the surface's accessible name. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows a skeleton sheet and notes; error shows Try again. |
| onRetry | () => void | — | Try again was pressed after the sheet failed to load. |
| height | number | 540 | The surface's height in px; the grid and the notes scroll inside it. |
| disabled | boolean | false | Read only: notes open and read; nothing is added, replied to or resolved. |
| className | string | — | Classes for the surface. |