> ## Documentation Index
> Fetch the complete documentation index at: https://tally.outhebox.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Your first grid

> Render a typed, filterable grid from an array of rows.

A grid needs three things: a schema that describes the columns, a data source that answers queries, and the `<Tally>` component.

**1. Describe the columns**

`createSchema` validates the columns and resolves each one to a column type. `key` names the field that identifies a row. A column without a `type` is `text`.

```ts schema.ts theme={null}
import { createSchema } from 'tally-core'

export const schema = createSchema({
  key: 'id',
  columns: [
    { id: 'account', label: 'Account', pin: 'left' },
    { id: 'owner', type: 'person' },
    { id: 'plan', type: 'enum', options: [{ value: 'free' }, { value: 'pro' }, { value: 'team' }] },
    { id: 'mrr', label: 'MRR', type: 'currency', aggregate: 'sum' },
    { id: 'growth', type: 'percent' },
    { id: 'renewal', type: 'date' },
  ],
})
```

`aggregate: 'sum'` puts the sum of MRR in the footer. `pin: 'left'` keeps the account column in view while scrolling.

**2. Provide the rows**

Rows are plain objects keyed by column id. Empty cells are `null`, `undefined` or `''`.

```ts rows.ts theme={null}
export const rows = [
  {
    id: 1,
    account: 'Acme Corp',
    owner: 'Ada Okafor',
    plan: 'team',
    mrr: 4200,
    growth: 12.5,
    renewal: '2026-11-02',
  },
  {
    id: 2,
    account: 'Globex',
    owner: 'Bruno Silva',
    plan: 'pro',
    mrr: 1800,
    growth: -3.2,
    renewal: '2027-01-15',
  },
  { id: 3, account: 'Initech', owner: null, plan: 'free', mrr: 0, growth: 0, renewal: null },
]
```

**3. Render the grid**

`createArraySource` filters, sorts, groups and aggregates the rows in memory. `useGrid` creates the grid, loads it on mount and disposes it on unmount.

```tsx accounts.tsx theme={null}
import { useState, type ReactNode } from 'react'
import { createArraySource } from 'tally-core'
import { Tally, useGrid } from 'tally-react'
import 'tally-react/styles.css'
import { rows } from './rows'
import { schema } from './schema'

export function Accounts(): ReactNode {
  const [source] = useState(() => createArraySource(rows, schema))
  const grid = useGrid({ schema, source })
  return <Tally grid={grid} label="Accounts" />
}
```

`label` is the accessible name of the grid, and the name of exported CSV files.

> [!NOTE]
>
> Create the source once. `useGrid` recreates the grid when `schema` or `source` change identity, so
> a source created on every render reloads the grid on every render.

## Type into the filter bar

The filter bar takes the query language. Each line below is one query.

```text theme={null}
plan is pro or team
mrr > 2k
renewal last 30 days
owner is empty
acme, plan is pro
plan is pro and mrr between 1k and 5k
sort by mrr desc
group by plan
```

Column names resolve by id, label or alias, so `MRR > 2k` and `mrr > 2k` are the same condition. Bare words such as `acme` search every searchable column. Parse errors carry the character offset of the problem.

## Next

> [card] [Data sources](https://tally.outhebox.dev/docs/data-sources.md)
>
> Point the grid at a server instead of an array.

> [card] [Query language](https://tally.outhebox.dev/docs/concepts/query-language.md)
>
> Everything the filter bar understands.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.