shadcn.io is not affiliated with official shadcn/ui
Family Plan Members Billing Block for Ark UI
Six seats on a family plan with a swap allowance: replace a member to free a seat (two swaps a month), add an email to an open seat for free, and the counter shows the swaps remaining and when they reset.
Installation
Overview
Keep a shared plan honest without a seat manager, with this family plan for React with Ark UI. A compact header states the rule, and one hairline frame with a plus mark on each corner holds six seat cells on shared hairlines, three by two. A member is an Avatar with initials, a name, an address, a badge (Organiser, Member or Invited) and an outline Replace button. Replacing frees the seat into "Swap in progress" and uses one of two swaps a month; an open seat or a seat in swap has an email Input that is checked for shape and for a duplicate, with the message in a slot that is always there. Adding to an open seat is free. The heading row counts the swaps remaining and the day they reset, "1 of 2 swaps remaining this month, resets 1 Nov". The only Shine Button, "Invite", fills every seat you typed an address into. It's built from Avatar, Input, Field, Badge, Shine Button and Guides, with a CSS entrance only. For family and household plans, shared licences and team seats without roles.
FAQ
How do I change the seats and the allowance?
Edit SEATS (a name, an address and a kind for each: member, open or swap) at the top of the file, SWAP_LIMIT for the swaps a month, SWAPS_USED for those already used and RESETS for the day the counter resets. The counter, the labels and every message are derived from the seats.
What counts as a swap?
Only Replace. It removes a member and frees the seat into swap; the swap is used then, not when you add the new address. Adding an address to an open seat, or to a seat already in swap, uses none. When no swaps remain, Replace stays focusable and says so, and open seats can still be filled.
What does Invite do, and what stays local?
It turns every seat with a valid address into an invited member and says that nothing was sent. A bad address keeps its seat, shows its message and takes focus. In your app, send the invitations from invite, keep the pending ones with their status and show the provider's answer in the same status line.
What does the address check do?
It checks the shape (something, an at sign, a domain with a dot) and that the address does not already have a seat. It does not check that the mailbox exists: only an invitation can. The message appears under the field and the field is marked invalid.