Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion apps/website/app/docs/_nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ export interface DocsNavSection {
export const docsNav: DocsNavSection[] = [
{
title: "Getting Started",
items: [{ title: "Install + first grid", href: "/docs/getting-started" }],
items: [
{ title: "Install + first grid", href: "/docs/getting-started" },
{ title: "Concepts", href: "/docs/getting-started/concepts" },
],
},
{
title: "Theming",
items: [
{ title: "Overview", href: "/docs/theming" },
{ title: "Pick a theme", href: "/docs/theming/pick-a-theme" },
{ title: "Override tokens", href: "/docs/theming/override-tokens" },
{ title: "Light / dark", href: "/docs/theming/light-dark" },
{ title: "Density", href: "/docs/theming/density" },
{ title: "Custom themes", href: "/docs/theming/custom-themes" },
{
title: "Tailwind + CSS-in-JS",
href: "/docs/theming/tailwind-css-in-js",
},
{ title: "Token reference", href: "/docs/theming/token-reference" },
],
},
];
48 changes: 48 additions & 0 deletions apps/website/app/docs/getting-started/concepts/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: Concepts
description: The mental model behind Pretable — a headless engine, a theming layer, and a streaming pipeline that compose through CSS variables and React hooks.
---

# Concepts

Pretable is built around three layers. Understanding their boundaries makes the rest of the docs easier to navigate.

## The engine + theming split

`@pretable/react` is the **engine**. It handles row virtualization, column layout, focus and selection state, sort and filter dispatch, and the data attributes (`[data-pretable-*]`) that mark every interactive element. The engine is structurally pure — its inline styles handle position, height, and z-index, but no colors, padding, fonts, or borders.

`@pretable/ui` is the **theming layer**. It ships CSS files: theme files (`themes/excel.css`, `themes/material.css`) declare `--pretable-*` design tokens at `:root`; `grid.css` is a selector-based skin that targets the engine's data attributes and resolves the tokens. There's no JavaScript theme builder; everything is CSS.

The two packages cooperate through a documented contract: the engine emits known data attributes, the theming layer styles them. Either side can be replaced without forking the other — you can ship `@pretable/ui` with a different design system, or use `@pretable/react` with hand-rolled CSS, and both still work.

## Two CSS-variable namespaces

Pretable uses one CSS-variable namespace exclusively: `--pretable-*`. All 24 public tokens live there. See the [token reference](/docs/theming/token-reference) for the full list.

Your application has its own namespace — whatever brand tokens you already use, like `--brand-*`, `--app-*`, or your design system's prefix. The two never collide because they're prefix-disjoint. You can theme the grid with Material 3 while your surrounding marketing site uses your own brand tokens, and there's no leakage either direction.

## Three runtime axes

Pretable's theming surface has three axes that can change at runtime:

- **Theme** — pick `excel.css` or `material.css` at build time by importing one. Switch between themes by swapping which CSS file is active (or by setting CSS variables manually if you want a hybrid).
- **Density** — toggle `data-density="compact"`, `data-density="standard"`, or `data-density="spacious"` on `<html>` to switch row heights, padding, and font sizes. The engine reads the new heights from CSS via `MutationObserver` and re-renders.
- **Light / dark** — toggle `data-theme="dark"` on `<html>` to activate Material's dark variant. Excel is light-only by design.

All three compose. `<html data-theme="dark" data-density="spacious">` gives you Material dark in spacious density. CSS specificity handles the cascade; no JavaScript coordination needed beyond the attribute toggles.

## Streaming

Pretable can render rows that arrive over the network — JSON streams, transactions API, server-sent events. The streaming pipeline is implemented but the public API is in flux for v0.0.x. See [Streaming Overview](/docs/streaming) for the current state, or browse `apps/streaming-demo/` in the repo for working code.

## Pre-1.0 caveat

> Pretable ships at version 0.0.x. Token names in `@pretable/ui` may rename or remove in any patch release. The engine API in `@pretable/react` is more stable but not frozen. Check the changelog before upgrading.

## Where to go next

- [Theming Overview](/docs/theming) — the customization surface, in detail.
- [Override tokens](/docs/theming/override-tokens) — change individual `--pretable-*` values without forking a theme.
- [Token reference](/docs/theming/token-reference) — the canonical list of all 24 tokens.

For the architectural rationale (why CSS variables, why density-coupled-to-theme, etc.), the design spec at `docs/superpowers/specs/2026-05-02-pretable-docs-architecture-design.md` has the why behind the what.
21 changes: 20 additions & 1 deletion apps/website/app/docs/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

Pretable is a React grid built around a deterministic engine. Selection survives filters; scroll stays at 60fps with 500k rows.

Start with the [getting-started guide](/docs/getting-started) for installation and your first grid. More guides land in future releases.
## Sections

### Getting Started

[Install + first grid](/docs/getting-started) walks you through `npm install` to a rendered three-column, five-row grid in five minutes. [Concepts](/docs/getting-started/concepts) is a one-page mental-model overview of the engine + theming + streaming layers — read it before diving into customization.

### Theming

[Theming](/docs/theming) covers the full customization surface: pick a prebuilt theme (Excel or Material 3), override individual `--pretable-*` tokens, switch light/dark and density at runtime, and author your own themes from a template. The [token reference](/docs/theming/token-reference) is the canonical 24-token table.

### Coming soon

The rest of the docs surface lands in subsequent releases:

- **Grid** — the engine API: columns, rows, selection, focus, sort, filter, density helpers.
- **Streaming** — transactions API and JSON streaming.
- **API Reference** — `PretableGrid` props, column types, theme token contract, density helpers.
- **Examples** — live, copy-pasteable patterns: basic table, light/dark switcher, density picker.

## Source

The codebase, including this site, lives at [github.com/cacheplane/pretable](https://github.com/cacheplane/pretable).
149 changes: 149 additions & 0 deletions apps/website/app/docs/theming/custom-themes/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
title: Custom themes
description: Author a theme file from scratch — define all 24 tokens at :root, optionally add density and dark-mode variants.
---

# Custom themes

When overrides grow beyond a handful of tokens, switch from override-on-top-of-existing to authoring a theme file from scratch. The contract is straightforward: define all 24 `--pretable-*` tokens at `:root`. Optionally add `[data-density="..."]` and `[data-theme="dark"]` blocks for runtime variants.

## Start from a template

Copy `excel.css` or `material.css` from `node_modules/@pretable/ui/themes/` into your project. They're hand-readable CSS — no preprocessing, no build step needed. Rename the file (e.g., `themes/brand.css`) and tweak the values.

```css
/* themes/brand.css */
:root {
/* Surfaces */
--pretable-bg-grid: #ffffff;
--pretable-bg-grid-alt: #fafafa;
--pretable-bg-header: #f0f0f0;
--pretable-bg-toolbar: #f0f0f0;
--pretable-bg-tooltip: #ffffff;

/* Text */
--pretable-text-cell: #1a1a1a;
--pretable-text-header: #404040;
--pretable-text-dim: #6a6a6a;

/* Lines */
--pretable-rule: #d8d8d8;
--pretable-rule-strong: #a0a0a0;
--pretable-radius: 4px;

/* State */
--pretable-bg-hover: #f5f5f5;
--pretable-bg-selected: rgba(255, 87, 34, 0.1);
--pretable-text-selected: #1a1a1a;
--pretable-focus-ring: #ff5722;

/* Accent */
--pretable-accent: #ff5722;

/* Density */
--pretable-row-height: 28px;
--pretable-header-height: 32px;
--pretable-cell-padding-x: 8px;
--pretable-cell-padding-y: 4px;
--pretable-font-size-cell: 14px;
--pretable-font-size-header: 12px;

/* Typography */
--pretable-font-sans:
"Inter", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
--pretable-font-mono: "JetBrains Mono", ui-monospace, Consolas, monospace;
}
```

Import your theme file instead of (or alongside) the prebuilt themes:

```css
@import "./themes/brand.css";
@import "@pretable/ui/grid.css";
```

## Add a dark-mode variant

If your theme should have dark mode, add a `[data-theme="dark"]` block. Override only the color tokens (density and typography typically inherit from light):

```css
[data-theme="dark"] {
/* Surfaces */
--pretable-bg-grid: #1a1a1a;
--pretable-bg-grid-alt: #1f1f1f;
--pretable-bg-header: #2a2a2a;
--pretable-bg-toolbar: #2a2a2a;
--pretable-bg-tooltip: #2a2a2a;

/* Text */
--pretable-text-cell: #e8e8e8;
--pretable-text-header: #c0c0c0;
--pretable-text-dim: #888888;

/* Lines */
--pretable-rule: #3a3a3a;
--pretable-rule-strong: #585858;

/* State */
--pretable-bg-hover: #252525;
--pretable-bg-selected: rgba(255, 138, 101, 0.15);
--pretable-text-selected: #ffffff;
--pretable-focus-ring: #ff8a65;

/* Accent */
--pretable-accent: #ff8a65;
}
```

Toggle `data-theme="dark"` on `<html>` to activate. See [Light / dark switching](/docs/theming/light-dark) for the React wiring.

## Add density variants

If you want compact/standard/spacious tiers, add explicit blocks for the non-default tiers. Don't redefine the natural default — let `:root` handle that.

If your `:root` is the standard tier:

```css
[data-density="compact"] {
--pretable-row-height: 22px;
--pretable-header-height: 26px;
--pretable-cell-padding-x: 6px;
--pretable-cell-padding-y: 2px;
--pretable-font-size-cell: 13px;
--pretable-font-size-header: 11px;
}

[data-density="spacious"] {
--pretable-row-height: 40px;
--pretable-header-height: 48px;
--pretable-cell-padding-x: 16px;
--pretable-cell-padding-y: 12px;
--pretable-font-size-cell: 14px;
--pretable-font-size-header: 13px;
}
```

Skip the `[data-density="standard"]` block since `:root` already provides those values. CSS handles the rest — when the user sets `data-density="standard"`, no rule matches and the cascade falls back to `:root`.

## Validate against the contract

Pretable doesn't enforce that custom themes define all 24 tokens — if you skip one, that token resolves to its default value (which depends on the engine's fallback) or to whatever else is in scope. The smoke test in `@pretable/ui` verifies that each shipped theme defines every documented token; if you want similar validation for your custom theme, copy that test pattern.

The full token list is in [Token reference](/docs/theming/token-reference).

## When to ship a custom theme as a separate file vs. override at `:root`

| Approach | When to use |
| --------------------------------------- | ---------------------------------------------------------------------- |
| Override individual tokens at `:root` | 1-5 tokens differ from a prebuilt theme. Stay close to Excel/Material. |
| Author a custom theme file from scratch | Most or all tokens differ. You want a dedicated brand theme. |
| Both | Custom theme as the base, occasional overrides for dark-only tweaks. |

For most apps, overriding individual tokens at `:root` is sufficient. Switch to a custom theme file when the override block grows past ~10 tokens or when you want to share your theme across multiple apps.

## Where to go next

- [Override tokens](/docs/theming/override-tokens) — when single-token overrides are enough.
- [Token reference](/docs/theming/token-reference) — the canonical 24-token list.
- [Light / dark switching](/docs/theming/light-dark) — toggle the dark variant of your custom theme.
- [Density switching](/docs/theming/density) — runtime tier toggle for your custom density blocks.
112 changes: 112 additions & 0 deletions apps/website/app/docs/theming/density/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: Density switching
description: Runtime row height, padding, and font size toggle via data-density attribute. Engine reads new values via MutationObserver and re-renders.
---

# Density switching

Pretable supports three density tiers — compact, standard, spacious — selected by the `data-density` attribute on `<html>`. Both Excel and Material define their own values per tier; switching is a single attribute toggle.

## How it composes

Each theme's natural default lives at `:root`:

- **Excel** defaults to compact (20px rows). `[data-density="standard"]` and `[data-density="spacious"]` blocks override for tighter or roomier modes.
- **Material** defaults to standard (48px rows). `[data-density="compact"]` and `[data-density="spacious"]` blocks override.

When the consumer sets `data-density="standard"` on `<html>` while Excel is loaded, the standard block wins (more specific selector than `:root`). When the consumer removes the attribute, the standard block stops matching and the `:root` (compact) values reassert.

> Density values are theme-coupled by design. Excel's "compact" (20px row) is tighter than Material's "compact" (40px row) because each theme's identity includes its own density character. Picking compact in Excel and compact in Material gives you different absolute heights — the relationship is what's preserved across themes.

## React state-driven

Same pattern as light/dark — hold density in state, sync to the DOM:

```tsx
import { useEffect, useState } from "react";

type Density = "compact" | "standard" | "spacious";

export function DensityPicker({
density,
onChange,
}: {
density: Density;
onChange: (density: Density) => void;
}) {
useEffect(() => {
document.documentElement.dataset.density = density;
}, [density]);

return (
<div role="radiogroup" aria-label="Row density">
<button onClick={() => onChange("compact")}>Compact</button>
<button onClick={() => onChange("standard")}>Standard</button>
<button onClick={() => onChange("spacious")}>Spacious</button>
</div>
);
}
```

Wrap your app with a `<DensityPicker density={...} onChange={...} />` and hold the state in your app's root or persist it to localStorage.

## The engine bridge

Two density tokens are read by the engine in JavaScript, not just by CSS:

- `--pretable-row-height` — used by the row virtualizer to compute `top` positions.
- `--pretable-header-height` — used to position the sticky header and compute body viewport height.

The engine reads these via the `useResolvedHeights` hook (in `@pretable/react`), which subscribes to attribute changes on `<html>` via `MutationObserver`. When you flip `data-density`, the engine re-renders the grid with new heights automatically — no need to pass `rowHeight` or `headerHeight` props from your component.

If you want to override the heights at the component level (e.g., a single grid uses spacious while the rest of the app respects user preference), pass props:

```tsx
<PretableGrid rows={rows} columns={columns} rowHeight={56} headerHeight={64} />
```

Numeric props win over CSS-resolved values. See [Density helpers](/docs/grid/density-helpers) for the full hook API (lands in PR 2 of the docs).

## Composition with light/dark

Density and theme variants are independent. `<html data-theme="dark" data-density="compact">` gives you Material dark in compact density. The cascade resolves cleanly because `[data-theme="dark"]` overrides only colors and `[data-density="compact"]` overrides only density tokens.

## Persisting density across reloads

Most apps persist density to localStorage:

```tsx
import { useEffect, useState } from "react";

type Density = "compact" | "standard" | "spacious";

function readDensity(): Density {
if (typeof window === "undefined") return "standard";
const stored = window.localStorage.getItem("pretable-density");
return stored === "compact" || stored === "spacious" ? stored : "standard";
}

export function useDensity() {
const [density, setDensity] = useState<Density>(readDensity);

useEffect(() => {
document.documentElement.dataset.density = density;
window.localStorage.setItem("pretable-density", density);
}, [density]);

return [density, setDensity] as const;
}
```

Use the hook in your density picker:

```tsx
const [density, setDensity] = useDensity();
return <DensityPicker density={density} onChange={setDensity} />;
```

## Where to go next

- [Override tokens](/docs/theming/override-tokens) — change density values themselves, not just which tier is active.
- [Light / dark switching](/docs/theming/light-dark) — composes with density.
- [Density helpers](/docs/grid/density-helpers) — `useResolvedHeights` and `getDensityHeights` API (PR 2 of the docs).
Loading
Loading