Tables
Table screens for any list of records: customers and a virtualized orders grid.
Installation & usage
npx @youj/cli add app-customersAlso 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:
'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
}}
/>
)
}Description
Browse every customer account with health, revenue, seats, usage trends and owners in one grid, then assign, tag or archive them in bulk.
Browse every customer account with health, revenue, seats, usage trends and owners in one grid, then assign, tag or archive them in bulk.
Work through every order in one virtualized grid: filter by payment, fulfillment or channel, open an order in a side sheet, and pack, ship or print labels in bulk.
Work through every order in one virtualized grid: filter by payment, fulfillment or channel, open an order in a side sheet, and pack, ship or print labels in bulk.
Code
Customers, the Customers story: the files youj add writes.
blocks/app
Reference
Props40 props, 2 types
Props
| Prop | Type | |
|---|---|---|
theme | ThemeInputA theme to force on the block. Default: none, so it inherits the app's (YoujProvider or a ThemeScope around it). | – |
embedded | booleanA 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 |
headingLevel | 1 | 2 | 3The 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 |
customers | Customer[]The accounts, one row each. Passing a new array replaces the rows. Default: | – |
count | numberHow many demo accounts to show without default 64 | 64 |
loading | booleanLoading, before the accounts arrive: skeleton stats and rows. default false | false |
error | ReactNodeReplaces the rows with an error panel; a string is its message. Default: none. | – |
onRetry | () => voidThe error panel's Retry. Default: none. | – |
empty | ReactNodeReplaces the grid when there are no accounts. Default: the grid's empty state from | – |
copy | Partial<CustomersCopy>Any of the page's strings, to rewrite or translate. Default: the demo's English. | – |
locale | stringFormats numbers, money and dates. Default: the app's locale (YoujProvider's | – |
currency | stringThe currency MRR is in. default 'USD' | 'USD' |
summary | Partial<CustomersSummary>The quarter's target and last month's figures, for the stats. Default: the demo's. | – |
tags | Record<string, { label: string }>The tags rows can carry, by key, with their labels. default the demo's seven | the demo's seven |
healths | DataGridStatusOptionsThe health values rows can carry, by key, with their labels and tones, in sort order. Default: healthy, trial, at risk and churning (labels from | – |
renewals | DataGridStatusOptionsThe 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 | – |
views | DataGridView[]The saved views, as tabs. Default: All, At risk, Enterprise and Renewing soon, counted from the rows. | – |
view | stringThe open view's id (controlled). Default: uncontrolled. | – |
defaultView | stringThe view open at first, uncontrolled. Default: the first. | – |
onViewChange | (view: string) => voidCalled when another view opens. | – |
selected | string[]The selected rows' ids (controlled). Default: uncontrolled. | – |
defaultSelected | string[]The rows selected at first, uncontrolled. Default: none. | – |
onSelectedChange | (ids: string[], rows: CustomerRow[]) => voidCalled when the selection changes, with the ids and the rows. | – |
now | Date | numberThe 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) => voidA click on a row, anywhere but its own controls. Default: none (the row's chevron opens its panel). | – |
getHref | (row: CustomerRow) => stringAn account's page: the panel's View account becomes a link to it. Default: no links. | – |
linkComponent | ElementTypeRenders internal links, e.g. Next.js default 'a' | 'a' |
onOpen | RowOpen account (row menu) and View account (panel). Default: a demo toast. | – |
onEmail | RowEmail contact. Default: a demo toast. | – |
onAssign | RowsAssign owner, from a row or the selection. Default: a demo toast. | – |
onTag | RowsAdd tag, from a row or the selection. Default: a demo toast. | – |
onExport | RowsExport the selection. Default: a demo toast. | – |
onArchive | RowsArchive: the rows leave at once and come back if it rejects. Default: archived here only. | – |
onRestore | RowsUndo after an archive: the rows come back. Default: restored here only. | – |
onAutoRenewChange | (row: CustomerRow, value: boolean) => Promise<unknown> | unknownA row's auto-renew switch: it flips at once and flips back if this rejects. Default: a demo toast. | – |
onImport | () => Promise<unknown> | unknownThe header's (and the empty state's) Import button. Default: does nothing. | – |
onCreate | () => Promise<unknown> | unknownThe header's New customer button. Default: does nothing. | – |
actions | ReactNodeHeader actions, replacing Import and New customer. Default: those two. | – |
classNames | Partial<Record<'header' | 'stats' | 'grid', string>>Classes on key parts. | – |
Customer
| Prop | Type | |
|---|---|---|
idRequired | string | – |
nameRequired | string | – |
domainRequired | string | – |
planRequired | Plan | (string & {})The plan's name: the demo's are Enterprise, Business, Pro and Starter. | – |
healthRequired | Health | – |
mrrRequired | numberMonthly recurring revenue, in dollars. | – |
seatsRequired | number | – |
seatLimitRequired | number | – |
usageRequired | number[]Weekly active users over the last 12 weeks. | – |
ownersRequired | DataGridPerson[] | – |
tagsRequired | string[] | – |
regionRequired | string | – |
renewsAtRequired | stringAn ISO date. | – |
renewalRequired | Renewal | – |
lastSeenRequired | stringAn ISO date. | – |
autoRenewRequired | boolean | – |
contactRequired | { name: string; email: string; role: string } | – |
ticketsRequired | number | – |
npsRequired | number | – |
CustomersSummary
| Prop | Type | |
|---|---|---|
mrrTargetRequired | numberThe quarter's MRR target. | – |
quarterPaceRequired | numberHow far through the quarter today is, 0–1: the target meter's notch. | – |
lastMonthCountRequired | numberAccounts a month ago, for the change under the count. | – |
mrrChangeRequired | numberMRR's change since last month, as a fraction (0.064 is +6.4%). | – |
riskChangeRequired | numberThe 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.
sectionperiodtitledescriptionimportcreatenounemptyTitleemptyDescriptionemptyActionviewscolumnsperMonthhealthrenewalactionsbulkundofailedmanyarchivedautoRenewOnautoRenewOffdraftingopeningchooseOwnertaggingexportedexportFilestatscustomersvsLastMonthnoAccountsmrrnothingBilledtoGoofTargetatRiskaccountsnothingAtRiskexposedrenewingupForRenewaloverduedueThisMonthcontactemailviewAccountusageusageHeadingusageLabelticketsnpsregionseatsseatsOfAccessibilitylandmarks, 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.