# Headless UI

At Spreadsheet, we understand the importance of flexibility, which is why our Headless UI approach allows you to use your preferred state management library with all components. Whether you prefer immer, redux, or any other state management library, you can seamlessly integrate it into your workflow.

For those who prefer the ease of use that comes with a pre-built solution, we offer the `@rowsncolumns/spreadsheet-state` package. This package uses immer to manage spreadsheet state, including sheets, sheetData, and more.

Additionally, it provides functionality for undo/redo, broadcasting history for collaboration, and easy integration with formula parser and evaluation libraries. For a full list of hooks that trigger side effects like formula calculation, history, and broadcasting, check out useSpreadsheetState.

Feel free to take a look at `useSpreadsheetState` for all hooks that triggers side effects (formula calculation, history and broadcasting)

## CellData

`CellData` is the per-cell record stored in the array/record that builds sheet data. It bundles everything we need to round-trip a cell: the user-entered value, the calculated value, formatting, hyperlinks, validation, comments, protection flags, pivot/expand state, and a handful of fields the spreadsheet engine uses internally.

Most fields exist in both a **long form** (`userEnteredValue`, `effectiveValue`, `userEnteredFormat`, `formattedValue`) and a **short form** (`ue`, `ev`, `uf`, `fv`). The short form is preferred for new writes — it keeps serialized payloads compact and is what the toolkit emits on save. The long forms still load, so older saved data keeps working.

A typical cell only sets one or two fields — most are optional and reserved for specific features.

```typescript
export type CellData<
  T extends StructuredResult = StructuredResult,
  M extends Mention = Mention,
> = {
  /**
   * The value the user entered. e.g. 1234, "Hello", or "=NOW()". Dates,
   * times, and datetimes are represented as doubles in serial format.
   */
  ue?: ExtendedValue;
  /**
   * The effective value. For formula cells, this is the calculated
   * result. For literal cells, equal to `ue`. Read-only — the
   * calculation engine writes this; do not set manually unless you're
   * hydrating from a saved snapshot.
   */
  ev?: ExtendedValue & StructuredValue<T>;
  /**
   * The formatted display string (after the number format is applied).
   * Read-only.
   */
  fv?: string;
  /**
   * The user-entered format. New writes are merged onto any existing
   * format. Can be a full `CellFormat` or a `StyleReference` (a short
   * `{ sid }` ref into the workbook's cellXfs registry) — the registry
   * dedupes repeated formats.
   */
  uf?: CellFormat | StyleReference;
  /**
   * Shared-strings key for cells whose value is interned in the
   * workbook's shared-strings table. Always a string.
   */
  ss?: string;
  /**
   * Text-format runs for rich-text cells — colored / bold / italic
   * segments and `@user` mentions.
   */
  textFormatRuns?: TextFormatRun<TextFormat, M>[];
  /**
   * Hyperlink target. Either a plain URL string or a structured
   * value carrying the URL + display label + tooltip.
   */
  hyperlink?: string | HyperlinkValue;
  /**
   * Data-validation rule attached to the cell. Inline rule object or
   * an ID reference into the sheet-level validation registry.
   */
  dataValidation?: DataValidationRule | DataValidationRuleRecord["id"];
  /**
   * Plain-text note (Excel "comment" — single-author, not threaded).
   */
  note?: string;
  /**
   * Pointer into the threaded-comment store (Excel 2016+ replies and
   * resolved state).
   */
  commentThreadId?: string | number;
  /**
   * Citation reference (FILTER source attribution, etc.).
   */
  citationId?: Citation["id"];
  /**
   * Cell-level protection flags. Only take effect when sheet protection
   * is enabled. `locked` defaults to true in Excel, so this field is
   * usually set to `{ locked: false }` to opt a cell OUT of protection.
   */
  protection?: {
    locked?: boolean;
    hidden?: boolean;
  };
  /**
   * Image URL — for cells whose value is an embedded image.
   */
  imageUrl?: string;
  /**
   * Cell metadata marker — currently only "people" for mention chips.
   */
  metaType?: "people";
  /**
   * Array-formula spill range in A1 notation ("B5:B20"). Only set on
   * the anchor cell of an array formula; spilled cells in the range
   * carry no `af`. Round-trips to / from <f t="array" ref="..."/> in
   * xlsx.
   */
  af?: string;
  /**
   * Conditional-formatting results keyed by rule ID. Written by the
   * CF evaluator; consumers shouldn't set this directly.
   */
  conditionalFormattingResultById?: Record<string, CustomFormulaResult>;
  /**
   * Result of a formula-based data-validation rule. Same caveat as
   * `conditionalFormattingResultById`.
   */
  dataValidationResult?: CustomFormulaResult;
  /**
   * Outline / pivot grouping markers used by the canvas to render
   * expand-collapse chevrons.
   */
  expandable?: boolean;
  expanded?: boolean;
  /**
   * Pivot grouping key tuple for the cell.
   */
  groupKeys?: string[];
  /**
   * Number of children (for outline / pivot summary rows).
   */
  childrenCount?: number;
  /**
   * Pivot table this cell belongs to.
   */
  pivotId?: PivotTable["pivotId"];
  /**
   * Collaboration / sync metadata.
   */
  version?: number;
  updatedAt?: number;

  // -------------------------------------------------------------------
  // Long-form aliases — kept for backwards compatibility with older
  // saved payloads. Prefer the short forms (`ue` / `ev` / `uf` / `fv`)
  // for new writes.
  // -------------------------------------------------------------------
  /** @deprecated use `ue` */
  userEnteredValue?: ExtendedValue;
  /** @deprecated use `ev` */
  effectiveValue?: ExtendedValue & StructuredValue<T>;
  /** @deprecated use `fv` */
  formattedValue?: string;
  /** @deprecated use `uf` */
  userEnteredFormat?: CellFormat | StyleReference;
};
```

> **Effective format** is no longer persisted on `CellData`. The renderer derives it on the fly from `uf`, the cell's effective value, and (for formula cells) precedent formats — see `useSheetProperties.getEffectiveFormat`. The legacy `effectiveFormat` / `ef` fields are accepted for loading older data but should not be written.

## Using your own state

Here we are using React `useState` hook to manage Spreadsheet state.

> Do note that you will have to create your own undo/redo data structure for useState. Immer or MobX generates patches for each state update, which can then be undo'ed or redo'ed.

```tsx
import {
  SpreadsheetProvider,
  CanvasGrid,
  CellData,
  Sheet,
} from "@rowsncolumns/spreadsheet";
import { CellInterface } from "@rowsncolumns/grid";
import { useState } from "react";

export type SheetData<T extends CellData = CellData> = Record<
  string,
  RowData<T>[]
>;
export type RowData<T> = {
  values?: T[];
};

const MySpreadsheet = () => {
  const activeSheet = 1;
  const [sheetData, setSheetData] = useState<SheetData>({
    1: [
      {},
      {
        values: [
          {},
          {
            fv: "Foobar",
          },
        ],
      },
    ],
  });
  const [sheets, setSheets] = useState<Sheet[]>([
    {
      sheetId: 1,
      rowCount: 10,
      columnCount: 10,
      title: "Sheet 1",
    },
  ]);
  return (
    <CanvasGrid
      rowCount={1000}
      columnCount={1000}
      sheetId={activeSheet}
      onChange={(
        sheetId: number,
        cell: CellInterface,
        value: string,
        previousValue: string
      ) => {
        // Save it back to state or database
      }}
      getCellData={(sheetId: number, rowIndex: number, columnIndex: number) => {
        return sheetData[sheetId]?.[rowIndex].values?.[columnIndex];
      }}
    />
  );
};

const App = () => (
  <SpreadsheetProvider>
    <MySpreadsheet />
  </SpreadsheetProvider>
);
```

## Custom CellData

You can inject a custom CellData type to Spreadsheet, and all renderers and editors will infer this type.

```tsx
type CustomCellData extends CellData {
  myNewProperty?: boolean
}

const MySpreadsheet = () => {
  return (
    <CanvasGrid<CustomCellData>
      rowCount={10}
      columnCount={10}
      getCellData={(sheetId, rowIndex, columnIndex) => {
        return {
          myNewProperty: false
        }
      }}
    />
  )
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rowsncolumns.app/getting-started/headless-ui.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
