Arc
ComponentsBlocksDocsPricingTry the demo
Get Pro
Introduction
Installation
Theming
Motion
AI and MCP
Changelog
Metrics dashboardPro
KPI drilldownPro
Metric explorerPro
Revenue explorerPro
MRR waterfallPro
Cohort retentionPro
Usage forecastPro
Journey flowPro
Uptime statusNewPro
Choropleth explorerPro
Connected scatterPro
Horizon chartPro
Account healthNewPro
Market terminalPro
Budget variancePro
Small multiplesPro
Line replayPro
AI chatPro
AI composerPro
AI answerPro
Model pickerPro
Model arenaPro
Prompt studioPro
Document extractionPro
Agent memoryPro
AI side panelPro
Research runPro
AI insight cardNewPro
Catch upNewPro
AI feature launchNewPro
AI search barNewPro
AI setup wizardNewPro
Call insightsNewPro
Lead scoringNewPro
Agent runPro
Agent tasksPro
Agent approvalsPro
Agent routinesPro
Agent orchestratorPro
MCP serversPro
Knowledge basePro
Eval suitePro
Agent builderNewPro
Agent galleryNewPro
SidebarPro4
Page header
Command palette
Notification center
Selection toolbarPro
Empty states
Search resultsPro
Login and sign up5, free and Pro variants
Onboarding illustrationPro
OTP input
Multi-step formPro
Two-factor setupNewPro
Magic linkNewPro
Settings pagePro
Settings commandPro
Security settingsPro
Billing overviewPro
Usage billingPro
Cancel flowPro
Onboarding surveyNewPro
Spend alertNewPro
Team membersPro
Invite peoplePro
Team directoryPro
Roles and permissionsPro
Profile pageNewPro
API keysPro
WebhooksPro
IntegrationsPro
Environment variablesPro
Domain verifyNewPro
Webhook testerNewPro
Plan comparison
Pricing calculatorPro
Usage pricingPro
Cart drawerPro
Checkout summaryPro
Live checkoutPro
Seat managerNewPro
Credit top-upNewPro
Invoice studioPro
Wallet cardPro
Delivery trackerPro
Product listingPro
Product detailPro
Activity feedNewPro
Inbox triagePro
Support conversationPro
Support widgetPro
Help centerPro
Feature votingNewPro
File upload
Media playerPro
Week calendarPro
Availability pickerPro
Project boardPro
Release readinessPro
Kanban boardPro
Smart schedulerNewPro
Task breakdownNewPro
Goal trackerNewPro
Hero section
Hero signupPro
Scroll storyPro
Spotlight gridPro
Feature bentoPro
Comparison table
Logo marquee
Studio perspectivesPro
Stats band
Changelog feed
FAQ section
Contact section
Team showcasePro
Blog grid
Site header
Site footer
CTA section
Newsletter signup
Activity terrainPro
Revenue globePro
Customer galaxyPro
Semantic zoomPro
Layout morphPro
Exploded viewPro
What’s new74 new
BlocksSeat manager

Seat manager

Pro

A team seats and billing card where avatars and a seat meter show members, invites and open seats against what you pay for, a stepper rolls the prorated charge for today and the new monthly total live, and changes go through a confirm step before anyone is charged. Pending invites can be resent or revoked with undo, and when every seat is taken the card says so and offers to add one.

Live block · sample data

When to use

  • Letting a team admin add or remove paid seats with the price impact visible before paying.
  • Showing seat usage against purchased seats, including invites that reserve a seat.
  • Handling the seat limit at the moment an admin hits it, with a one tap way to add a seat.

When not to use

  • Managing roles and permissions for many members: use a member table and link to this card for seats.
  • Usage based or credit billing: use credit-topup or a usage meter instead.
  • Plan comparison or upgrades between tiers: use a pricing table.

Installation

Pro source and install commands unlock with a Pro plan.

Usage

Put it in the seats or members section of a team billing page. Pass `purchased` (the subscription quantity), `members`, `invites`, `plan` ({ name, pricePerSeat, interval, currency, maxSeats }) and the real billing `period` so the proration matches your billing system. Then wire `updateSeats(quote, signal)`, `resendInvite` and `revokeInvite` to your backend. Revoke runs after the undo window closes. If `updateSeats` rejects with an Error, its message appears in the footer with a Try again button. `buildQuote` is exported so your server can check the same numbers, and `onQuoteChange` lets you swap in your billing system's own invoice preview if you need it.

example.tsx
1import { SeatManager } from "@/components/arc/blocks/seat-manager/seat-manager";2 3export function TeamBilling({ team }: { team: Team }) {4  return <SeatManager5    workspace={team.name}6    plan={{ name: "Pro", pricePerSeat: 16, interval: "month", maxSeats: 25 }}7    purchased={team.seats}8    members={team.members.map(m => ({ id: m.id, name: m.name, avatar: m.imageUrl }))}9    invites={team.invites.map(i => ({ id: i.id, email: i.email, invitedBy: i.inviter, note: i.sentAgo }))}10    period={{ start: team.periodStart, end: team.periodEnd }}11    paymentMethod={{ brand: "Visa", last4: team.cardLast4 }}12    updateSeats={(quote, signal) => fetch("/api/billing/seats", { method: "POST", body: JSON.stringify({ seats: quote.target }), signal }).then(r => { if (!r.ok) throw new Error("Your card was declined. Nothing was charged."); })}13    resendInvite={(invite, signal) => fetch(`/api/invites/${invite.id}/resend`, { method: "POST", signal }).then(() => undefined)}14    revokeInvite={(invite, signal) => fetch(`/api/invites/${invite.id}`, { method: "DELETE", signal }).then(() => undefined)}15  />;16}
Built fromButtonAvatarTextShimmer

API reference

2 parts. The first is the root.

SeatManager

Team seats and billing in one card. Avatars and a seat meter show members, pending invites and open seats against what is purchased; a stepper changes the seat count while the prorated charge for today and the new recurring total roll live; pending invites can be resent or revoked with undo; and Update seats asks for a confirm step before paying. When every seat is taken it says so and offers one tap to add a seat. With no props it plays a simulated loop on a Pro workspace, and nothing is charged or sent.

PropTypeDefaultDescription
workspacestring"Halcyon"Workspace name shown under the title.
plan{ name: string; pricePerSeat: number; interval?: "month" | "year"; currency?: string; maxSeats?: number }Pro, $16 per seat per month, up to 25 seatsPlan badge, seat price in major units, billing interval, ISO currency and the seat cap. The plus button stops at maxSeats and explains why.
purchasednumber12Seats paid for right now. Becomes the new count after a successful update.
members{ id: string; name: string; avatar?: string }[]10 sample membersMembers holding a seat. The first four show as avatars, the rest as a +N count.
memberCountnumbermembers.lengthTotal members when `members` is only a sample for the avatars.
invites{ id: string; email: string; invitedBy?: string; note?: string }[]2 sample invitesPending invites. Each reserves a seat until accepted or revoked. `note` is a short muted suffix such as "2 days ago".
period{ start: Date | string; end: Date | string }Oct 18 to Nov 18 2026Current billing period. Added seats are prorated by the days left; the new total starts at `end`.
nowDatenew Date() (Oct 27 2026 in the demo)"Today" for proration.
paymentMethod{ brand: string; last4: string } | nullVisa ending 4242Named in the footer and the confirm step. Pass null to hide it.
receiptEmailstring"billing@halcyon.so"Named in the done message.
updateSeats(quote: SeatQuote, signal: AbortSignal) => Promise<void>–Applies the change. SeatQuote is { current, target, delta, dueToday, nextTotal, daysLeft, periodDays, renewsOn }. Reject with an Error and its message shows in the footer with Try again.
resendInvite(invite: SeatInvite, signal: AbortSignal) => Promise<void>–Sends the invite email again. The button shows a spinner, then Sent.
revokeInvite(invite: SeatInvite, signal: AbortSignal) => Promise<void>–Called when the undo window closes. Reject to put the invite back.
undoSecondsnumber3How long Undo stays on a revoked invite.
onQuoteChange(quote: SeatQuote) => void–Called whenever the stepper changes the quote.
onSeatsChange(seats: number, quote: SeatQuote) => void–Called after a change is applied.
onPhaseChange(phase: "idle" | "confirm" | "saving" | "done" | "error") => void–Called on every footer phase change.
autoPlaybooleantrue when no handlers are passedPlays add three seats, pay, resend and revoke on a loop, pausing off screen. Any pointer or key press inside the card hands control to the person.
classNamestring–Extra class on the card.

buildQuote

The proration math the card uses, exported so a server can check the same numbers: buildQuote(current, target, plan, period, now) returns a SeatQuote. Added seats cost delta x price x daysLeft / periodDays, rounded to cents; removals cost nothing today and apply at renewal.

No props.

Keyboard interactions

Tab
Moves through the stepper buttons, the seat count, invite actions and the footer buttons.
Arrow uporArrow down
On the seat count (a spinbutton), adds or removes one seat. Arrow right and left do the same.
Page uporPage down
On the seat count, changes by five seats within the limits.
HomeorEnd
On the seat count, jumps to the fewest seats allowed (members plus invites) or the plan maximum.
SpaceorEnter
Activates the focused button. Focus moves to Pay when the confirm step opens and back to Update seats after Back or a successful update.

Accessibility

  • The card is a labelled section. The seat count is a spinbutton with min, max, value and a spoken value such as "15 seats".
  • The seat meter is a single image with a full text label (used, members, invited, open, being added); the legend repeats the counts in text, so color is never the only signal.
  • Limits stay explained, not silent: pressing past the minimum or the plan cap keeps the button focusable (aria-disabled) and shows why in the notice line.
  • A polite live region announces review, update, resend, revoke and undo, and adds "Simulated, nothing was charged" (or sent) when no handlers are passed.
  • Rolling numbers render the real value for screen readers and hide the digit strips.
  • Every invite action has its own label, such as "Revoke invite to kai.tanaka@halcyon.so".

Motion

  • Numbers roll: each digit is a 0 to 9 strip that slides 240ms on an ease-out, with tabular numerals in a right-aligned slot so nothing shifts.
  • Seat cells grow in from zero width and shrink out on a 240ms spring with no bounce; neighbours glide to make room.
  • Text, icon and button swaps are single crossfades: old out in 100ms, new in over 160ms with a 2px rise.
  • A revoked invite crossfades to an undo row, then collapses its measured height on a bounce-free spring.
  • Press and hold on plus or minus repeats after 380ms, then every 70ms.
  • Reduced motion keeps every state and number, swaps instantly and drops the rolls and cell growth. The demo loop pauses off screen.

Responsive behavior

  • Fills its container up to 560px and adapts with a container query, not the viewport.
  • Below about 460px the padding tightens, the quote rows drop their muted notes and the legend hides the new seats item, which the notice line already states.
  • Emails, notes and footer text truncate with an ellipsis; amounts and counts never wrap.
  • Past 40 seats the meter groups cells into one segment per kind so it stays readable.

Performance

  • Plain DOM and CSS; digit rolls are CSS transforms and cell growth is a flex-grow spring.
  • Every request is abortable and aborted on unmount; timers are cleared on unmount.
  • The demo loop pauses when the card is off screen.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for the seats or members section of a team billing page in any per-seat SaaS. Pass the subscription quantity as purchased and the real billing period so proration matches your processor.
  • Proration here is a simple daily estimate. If your processor computes it (Stripe upcoming invoice preview), call it in onQuoteChange and pass the result to your confirm copy, or replace the numbers server side before charging.
  • Pending invites hold seats, so the minimum is members plus invites. Revoking frees a seat at once with undo; the revoke handler runs after the undo window.
  • Reject updateSeats with a human sentence ("Your card was declined. Nothing was charged."); it is shown verbatim with Try again.
/components/blocks/seat-manager/markdown

The full library index for assistants is at /llms.txt.

Related

Billing overviewProBlockA billing page where the plan card morphs into a picker and usage, invoices, and card follow.Credit top-upProBlockA card for buying AI credits. It shows the balance with a 14 day usage chart and how many days are left, three preset packs with bonus badges, a custom amount slider with a live price, auto top-up with a threshold and the saved Visa or Mastercard. When you buy, the new balance rolls in like an odometer.ButtonComponentA clear, responsive action with quiet secondary states.AvatarComponentA compact identity marker for people and accounts.Text shimmerComponentShow ongoing work with a calm light across the words.Checkout summaryProBlockAn order summary with a promo code that becomes a chip, discount and tax lines, and pay button states.
PreviousWebhook testerNextAccount health
Arc · 129 block pagesllms.txt

On this page

  • Preview
  • When to use
  • When not to use
  • Installation
  • Usage
  • API reference
  • Keyboard
  • Accessibility
  • Motion
  • Responsive behavior
  • Performance
  • Notes for AI
  • Related
View as Markdown

Seats

Pro

Halcyon · $16 per seat/mo · Renews Nov 18

+6
1201234567890123456789 of 1201234567890123456789 seats used
  • 1001234567890123456789 members
  • 20123456789 invited
  • 00123456789 open
Seat limit reached. New invites need a seat.
Seats1201234567890123456789 purchased
1201234567890123456789
Due todayNo change
$0.00$0123456789.01234567890123456789
From Nov 18Current total
$192.00/mo$012345678901234567890123456789.01234567890123456789/mo
Pending invites20123456789
  • kai.tanaka@halcyon.soInvited by Emma Collins, 2 days ago
  • priya.raman@halcyon.soInvited by Nathan Cole, expires Oct 29
Billed via Stripe, Visa ending 4242