Skip to content

Billing and team

Screens for the paying workspace: plan and invoices, usage and charges, and members with roles and seats.

Installation & usage

npx @youj/cli add app-billing

Also installs alert, app-page, async-button, badge, button, card, counter, dialog, field, icon-credit-card, icon-download, icon-lock, icon-refresh, input, presence-list, radio-group, reveal, skeleton, table, textarea, toast, toggle-group, typography, youj-core, youj-ui-core.

The CLI copies the block to blocks/app/billing.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:

billing-view.tsx
'use client'

import { Billing, type BillingProps } from '@/blocks/app/billing'

export function BillingView({ plans, cycle, card, invoices }: Pick<BillingProps, 'plans' | 'cycle' | 'card' | 'invoices'>) {
  return (
    <Billing
      plans={plans}
      cycle={cycle}
      card={card}
      invoices={invoices}
      copy={{ title: 'Billing' }}
      onRetry={() => {
        // your code
      }}
      onChangePlan={async (change) => {
        // await your API here; the block shows progress until it settles
      }}
      onUpdatePayment={async (input) => {
        // await your API here; the block shows progress until it settles
      }}
    />
  )
}

Reference

Props26 props, 5 types

Props

Billing 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
failedPaymentboolean

Starts with September's charge failed, which raises the payment alert. Only applies to the demo invoices.

default true

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

The product in the top bar.

default Tern

Tern
plansBillingPlan[]

The plans on offer, cheapest first.

default Starter, Growth, Scale

Starter, Growth, Scale
planstring

The current plan's id, to control it. Pair with onChangePlan.

–
defaultPlanstring

The current plan's id at first, when uncontrolled.

default 'growth'

'growth'
cycleBillingCycle
'monthly''annual'

The billing cycle, to control it.

–
defaultCycleBillingCycle
'monthly''annual'

The billing cycle at first, when uncontrolled.

default 'monthly'

'monthly'
seatsnumber

Seats the workspace pays for.

default 14

14
cardBillingCard

The card on file.

default a Visa ending 4242

a Visa ending 4242
invoicesBillingInvoice[]

Every invoice, newest first; one with status failed raises the payment alert. Default: Tern's seven.

–
detailsBillingInfo

Who invoices are addressed to.

default Tern Analytics GmbH

Tern Analytics GmbH
localestring

Formats every amount. Default: the app's locale (YoujProvider's locale), else en-US.

–
currencystring

The ISO currency of every amount.

default 'USD'

'USD'
headingLevel1 | 2 | 3

The page title's level; section headings sit one below.

default 1

1
loadingboolean

Shows skeleton rows in place of the invoices while they load.

default false

false
errorReactNode

A load failure, shown under the title with a Retry button when onRetry is set.

–
onRetry() => void

Called from the error's Retry button.

–
emptyReactNode

Replaces the empty state shown when there are no invoices.

default `copy.emptyInvoices`

`copy.emptyInvoices`
copyPartial<BillingCopy>

Any of the page's strings, to rewrite or translate.

–
onChangePlan(change: { plan: BillingPlan; cycle: BillingCycle }) => Promise<unknown> | unknown

Switches plan or cycle; resolve when done, throw to show the error in the dialog. Default: a demo wait.

–
onUpdatePayment(input: BillingCardInput) => Promise<BillingCard | void> | BillingCard | void

Saves a new card from the form; resolve with the card on file (or nothing to show what was typed). Default: a demo wait.

–
onRetryPayment(invoice: BillingInvoice) => Promise<unknown> | unknown

Retries a failed charge; resolve when paid, throw when declined. Default: a demo wait.

–
onDownloadInvoice(invoice: BillingInvoice) => Promise<unknown> | unknown

Fetches an invoice's PDF; resolve when it's ready. Default: a demo wait.

–
onSaveDetails(details: BillingInfo) => Promise<unknown> | unknown

Saves the billing details; resolve when saved, throw to show the error. Default: a demo wait.

–

BillingPlan

BillingPlan props
PropType
idRequiredstring–
nameRequiredstring–
blurbRequiredstring–
priceRequirednumber

Per seat per month, billed monthly, in the block's currency. Annual billing takes two months off.

–

BillingCard

BillingCard props
PropType
brandRequiredstring

e.g. Visa.

–
last4Requiredstring–
expMonthRequirednumber–
expYearRequirednumber

Four digits.

–
nameRequiredstring–

BillingCardInput

BillingCardInput props
PropType
nameRequiredstring–
numberRequiredstring–
expiryRequiredstring

MM/YY.

–
cvcRequiredstring–

BillingInvoice

BillingInvoice props
PropType
idRequiredstring–
numberRequiredstring

As printed, e.g. TRN-0010.

–
dateRequiredstring

As it reads, e.g. Sep 1, 2026.

–
periodRequiredstring

What it covers, e.g. Sep 2026.

–
amountRequirednumber

In the block's currency; negative for a credit.

–
statusRequiredBillingInvoiceStatus
'paid''due''failed''refunded'
–

BillingInfo

BillingInfo props
PropType
companyRequiredstring–
emailRequiredstring–
addressRequiredstring–
taxIdRequiredstring–
Strings98 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.

titledescriptionbreadcrumbplanBadgeerrorTitleretryfailedTitlefailedDescriptionretryPaymentchargingpaiddeclinedpaidToastchargedTocurrentPlanmonthlyannualchangePlanperSeatperMonthseatsnextInvoicenextInvoiceDaterenewsrenewsDatebilledMonthlybilledYearlychangePlanTitlechangePlanDescriptionbillingCycleannualSavingplanLabelcurrentBadgeperSeatMonthnoChangechangeFromcancelupdatingplanUpdatedconfirmChangeplanChangedinvoicesHeadinginvoicesLabelshowInvoicesfilterAllfilterPaidfilterOpennothingOpenemptyInvoicesloadinginvoiceColumndateColumnperiodColumnstatusColumnamountColumnactionsColumnstatusretryChargeretryFordownloadAspreparingdownloadedpaymentTitlepaymentDescriptionupdateendingIncardSummaryexpirescardUpdatedupdateCardTitleupdateCardDescriptionnameOnCardcardNumbercardNumberPlaceholderexpiryexpiryPlaceholdercvccvcPlaceholdernameErrornumberErrorexpiryErrorcvcErrorprocessorNotesavingcardSavedsaveCarddetailsTitledetailsDescriptioncompanyinvoiceEmailaddresstaxIdemailErrorresetsavedsaveDetailsdetailsSaveddetailsSavedNote
Built from18 components
Accessibilitylandmarks, dialogs, tables, status

Accessibility

The screens are a main with a header and labelled sections. The dialogs (change plan, payment method, invite) trap focus and close with Escape; payment errors appear in an alert and a plan change is announced in a live region. The members table has a label, a member’s role is a select labelled with their name, and each member’s actions are a menu labelled the same way. Seat usage is a labelled meter. Usage and charges names its chart and gives the same days as a table for screen readers; focus the chart and the arrow keys walk the days, opening the tooltip, and each SKU row says its usage and whether it is over the allowance in words, with the ring only decorative. Links and buttons are real a and button elements with the kit’s focus ring.