Database
Database
Databases as plain data plus React views: a typed collection schema, rows, saved views with filters and sorts, a state hook and seven layouts.
Data model
Everything a database renders comes from one DatabaseData object: { collection, rows, views }.
Collection:{ id, name, properties: Property[] }, the typed schema.Property: a discriminated union ontype:title,text,number(format),select/multi_select(options),status(optionswith agroup: todo / in_progress / complete),date(dateFormat),person(people),checkbox,url,email,phone,created_time.Row:{ id, values, icon? }.valuesis keyed by property id; dates are ISOYYYY-MM-DD, select/status store option ids, multi-select and person store id arrays.View:{ id, name, type, filters, sorts, groupBy?, visible?, widths?, dateProperty?, order?, chart? }.typeis one oftable,board,gallery,list,calendar,timeline,chart.Filter:{ id, propertyId, operator, value? }with operatorsis,is_not,contains,does_not_contain,is_empty,is_not_empty,before,after,checked,unchecked,gt,lt.Sort:{ propertyId, direction: "ascending" | "descending" }, applied in order.OptionColor: the option colors (default,gray,brown,orange,yellow,green,blue,purple,pink,red), mapped to token classes byoptionColors.
State: useDatabase
useDatabase(initial, initialViewId?) holds one database in React state (no persistence) and returns a DatabaseApi. Pass it to <DatabaseView db={...}> to share state between several views, or let DatabaseView create its own from data.
Views, cells and menus read the API through context: useDatabaseContext() (throws outside a database) or useOptionalDatabase() (returns null, so menus can also render on their own).
Query helpers
queryRows(collection, rows, view): filter, then sort, then the view’s manualorder.groupRows(collection, rows, groupBy):RowGroup[]per option (plus "No …" for empty values).matchesFilter,compareRows,visibleProperties,titleProperty,rowTitle,getProperty,findOption,optionsOf.operatorsFor[type]andoperatorLabels[op]drive the filter editor;valuesFromFilterspre-fills new rows so they match the active filters.- Dates:
parseDate,toISODate,formatDate,monthLabel,sameDay,monthGrid.
Sample data
createTasksSample() returns a fresh copy of the sample database "Tasks": 5 properties (Task, Status, Priority, Team, Due), 8 rows and one view of each type (v-table, v-board, v-gallery, v-list, v-calendar, v-timeline, v-chart). SAMPLE_TODAY is the sample’s reference date, 2026-10-15; pass it as today so calendar and timeline show the sample rows around today.
Usage
- Use the database module when content is a collection of records that people sort, filter and view in several layouts (tasks, projects, CRM rows).
- Start from
DatabaseViewwith aDatabaseDataobject; reach for the individual views and menus only when you build your own database chrome. - Share one
useDatabase()state between several views with<DatabaseView db={…}>so edits stay in sync. - Don’t use a database for a short static list or a single record; use a plain
Table, a list or a property list instead. - Don’t persist through
useDatabase; it holds state in memory. Savedb.datato your backend yourself.
Installation
Install the package (React 19):
Import the styles once, in your app entry or root layout:
Use it anywhere inside your app's <Theme> (see Installation):
Examples
Shared state across views
One useDatabase instance drives two blocks: edit a status in the table and the board follows.
API Reference
useDatabase
useDatabase(initial: DatabaseData | (() => DatabaseData), initialViewId?: string): DatabaseApi. The returned API:
| Prop | Type | Default |
|---|---|---|
data | DatabaseData | |
activeViewId | string | |
activeView | View | |
setActiveView | (id: string) => void | |
updateView | (id: string, patch: Partial<View> | ((v: View) => Partial<View>)) => void | |
addView | (type: ViewType) => View | |
updateRow | (rowId: string, propertyId: string, value: PropertyValue) => void | |
addRow | (values?: Row['values'], opts?: { viewId?: string; position?: 'start' | 'end' }) => Row | |
updateProperty | (id: string, patch: Partial<Property>) => void | |
addOption | (propertyId: string, name: string) => SelectOption | |
moveRow | (viewId: string, rowId: string, beforeRowId?: string) => void |
DatabaseProvider
Puts a DatabaseApi in context for bare views and cells. Read it with useDatabaseContext() or useOptionalDatabase().
| Prop | Type | Default |
|---|---|---|
value | DatabaseApi | |
children | ReactNode |
View
A saved layout of the collection.
| Prop | Type | Default |
|---|---|---|
id | string | |
name | string | |
type | "table""board""gallery""list""calendar""timeline""chart" | |
filters | Filter[] | |
sorts | Sort[] | |
groupBy | string | |
visible | string[] | all |
widths | Record<string, number> | title 280, others 200 |
dateProperty | string | first date property |
order | string[] | |
chart | { locked?: boolean; groupBy?: string } |
Accessibility
- Views render real roles: the table is
role="table"withrow/columnheader/cell; board columns arerole="list"withlistitemcards; calendar is agrid. - View tabs are a
tablistoftabbuttons witharia-selected; keyboard focus shows a 2px + 4px blue ring. - Menus are focusable surfaces: ArrowUp/ArrowDown move, Enter/Space activate (
useMenuKeys). Popovers (Radix) close on Escape and return focus to the trigger.