Skip to content

Tables

Table screens for any list of records: customers and a virtualized orders grid.

Installation & usage

npx @youj/cli add app-customers

Also installs app-page, async-button, avatar, badge, button, icon-archive, icon-arrow-up-right, icon-download, icon-mail, icon-plus, icon-tag, icon-upload, icon-user-plus, icon-users, reveal, sparkline, stat-card, tables-data-grid, toast, youj-core, youj-ui-core.

The CLI copies the block to blocks/app/customers.tsx with every component it uses, so it’s yours to edit.

Every prop is optional and falls back to the demo, so it renders as you see it until you pass your own. Give it your data, rewrite or translate its strings through copy, and connect its actions:

customers-view.tsx
'use client'

import Link from 'next/link'
import { Customers, type CustomersProps } from '@/blocks/app/customers'

export function CustomersView({ customers, tags, healths, renewals }: Pick<CustomersProps, 'customers' | 'tags' | 'healths' | 'renewals'>) {
  return (
    <Customers
      customers={customers}
      tags={tags}
      healths={healths}
      renewals={renewals}
      copy={{ section: 'Revenue' }}
      linkComponent={Link}
      onRetry={() => {
        // your code
      }}
      onViewChange={(view) => {
        // your code
      }}
      onSelectedChange={(ids, rows) => {
        // your code
      }}
    />
  )
}

Reference

Props40 props, 2 types

Props

Customers props
PropType
themeThemeInput

A theme to force on the block. Default: none, so it inherits the app's (YoujProvider or a ThemeScope around it).

–
embeddedboolean

A page in your app's frame: the shared page header (AppPage) and the frame's gutter, across the whole width, with no top bar of its own.

default true

true
headingLevel1 | 2 | 3

The title's heading level.

default 1

1
brand{ name: string; logo?: ReactNode }

The product in the standalone top bar.

default Tessera, with its initial as the logo

Tessera, with its initial as the logo
customersCustomer[]

The accounts, one row each. Passing a new array replaces the rows. Default: count seeded demo accounts.

–
countnumber

How many demo accounts to show without customers.

default 64

64
loadingboolean

Loading, before the accounts arrive: skeleton stats and rows.

default false

false
errorReactNode

Replaces the rows with an error panel; a string is its message. Default: none.

–
onRetry() => void

The error panel's Retry. Default: none.

–
emptyReactNode

Replaces the grid when there are no accounts. Default: the grid's empty state from copy.empty…, with an import.

–
copyPartial<CustomersCopy>

Any of the page's strings, to rewrite or translate. Default: the demo's English.

–
localestring

Formats numbers, money and dates. Default: the app's locale (YoujProvider's locale), else en-US.

–
currencystring

The currency MRR is in.

default 'USD'

'USD'
summaryPartial<CustomersSummary>

The quarter's target and last month's figures, for the stats. Default: the demo's.

–
tagsRecord<string, { label: string }>

The tags rows can carry, by key, with their labels.

default the demo's seven

the demo's seven
healthsDataGridStatusOptions

The health values rows can carry, by key, with their labels and tones, in sort order. Default: healthy, trial, at risk and churning (labels from copy.health). The stat cards still count those four.

–
renewalsDataGridStatusOptions

The renewal values rows can carry, by key, with their labels and tones, in sort order. Default: overdue, this month, next quarter and later (labels from copy.renewal). The stat cards still count overdue and this month.

–
viewsDataGridView[]

The saved views, as tabs. Default: All, At risk, Enterprise and Renewing soon, counted from the rows.

–
viewstring

The open view's id (controlled). Default: uncontrolled.

–
defaultViewstring

The view open at first, uncontrolled. Default: the first.

–
onViewChange(view: string) => void

Called when another view opens.

–
selectedstring[]

The selected rows' ids (controlled). Default: uncontrolled.

–
defaultSelectedstring[]

The rows selected at first, uncontrolled. Default: none.

–
onSelectedChange(ids: string[], rows: CustomerRow[]) => void

Called when the selection changes, with the ids and the rows.

–
nowDate | number

The moment relative dates count from.

default the demo's fixed Sep 29, 2026, so server and browser agree

the demo's fixed Sep 29, 2026, so server and browser agree
onRowClick(row: CustomerRow) => void

A click on a row, anywhere but its own controls. Default: none (the row's chevron opens its panel).

–
getHref(row: CustomerRow) => string

An account's page: the panel's View account becomes a link to it. Default: no links.

–
linkComponentElementType

Renders internal links, e.g. Next.js Link.

default 'a'

'a'
onOpenRow

Open account (row menu) and View account (panel). Default: a demo toast.

–
onEmailRow

Email contact. Default: a demo toast.

–
onAssignRows

Assign owner, from a row or the selection. Default: a demo toast.

–
onTagRows

Add tag, from a row or the selection. Default: a demo toast.

–
onExportRows

Export the selection. Default: a demo toast.

–
onArchiveRows

Archive: the rows leave at once and come back if it rejects. Default: archived here only.

–
onRestoreRows

Undo after an archive: the rows come back. Default: restored here only.

–
onAutoRenewChange(row: CustomerRow, value: boolean) => Promise<unknown> | unknown

A row's auto-renew switch: it flips at once and flips back if this rejects. Default: a demo toast.

–
onImport() => Promise<unknown> | unknown

The header's (and the empty state's) Import button. Default: does nothing.

–
onCreate() => Promise<unknown> | unknown

The header's New customer button. Default: does nothing.

–
actionsReactNode

Header actions, replacing Import and New customer. Default: those two.

–
classNamesPartial<Record<'header' | 'stats' | 'grid', string>>

Classes on key parts.

–

Customer

Customer props
PropType
idRequiredstring–
nameRequiredstring–
domainRequiredstring–
planRequiredPlan | (string & {})

The plan's name: the demo's are Enterprise, Business, Pro and Starter.

–
healthRequiredHealth–
mrrRequirednumber

Monthly recurring revenue, in dollars.

–
seatsRequirednumber–
seatLimitRequirednumber–
usageRequirednumber[]

Weekly active users over the last 12 weeks.

–
ownersRequiredDataGridPerson[]–
tagsRequiredstring[]–
regionRequiredstring–
renewsAtRequiredstring

An ISO date.

–
renewalRequiredRenewal–
lastSeenRequiredstring

An ISO date.

–
autoRenewRequiredboolean–
contactRequired{ name: string; email: string; role: string }–
ticketsRequirednumber–
npsRequirednumber–

CustomersSummary

CustomersSummary props
PropType
mrrTargetRequirednumber

The quarter's MRR target.

–
quarterPaceRequirednumber

How far through the quarter today is, 0–1: the target meter's notch.

–
lastMonthCountRequirednumber

Accounts a month ago, for the change under the count.

–
mrrChangeRequirednumber

MRR's change since last month, as a fraction (0.064 is +6.4%).

–
riskChangeRequirednumber

The at-risk count's change since last month, as a fraction.

–
Strings56 keys for copy

Strings

copy takes any of these keys; the rest keep their defaults. A key whose text holds a value (a count, a name) takes a function.

sectionperiodtitledescriptionimportcreatenounemptyTitleemptyDescriptionemptyActionviewscolumnsperMonthhealthrenewalactionsbulkundofailedmanyarchivedautoRenewOnautoRenewOffdraftingopeningchooseOwnertaggingexportedexportFilestatscustomersvsLastMonthnoAccountsmrrnothingBilledtoGoofTargetatRiskaccountsnothingAtRiskexposedrenewingupForRenewaloverduedueThisMonthcontactemailviewAccountusageusageHeadingusageLabelticketsnpsregionseatsseatsOf
Built from8 components, 1 blocks
Accessibilitylandmarks, grid, keyboard

Accessibility

Each screen has a header and one heading (headingLevel changes it). The records sit in the Data grid block, a role="grid" that reports aria-sort on sorted columns and aria-multiselectable when rows can be selected. Records grid opens a record in a sheet that traps focus and closes with Escape. Records grid shows an order’s progress as an ordered list with the current stage marked by aria-current. Links and buttons are real a and button elements with the kit’s focus ring.