Subscription State
Plan, price and what happens next. A cancelled subscription with three weeks of access left is the case people most often get wrong, and a status badge alone never says it.
src/components/ui/subscription-state.tsxAbout
From the component's own source, where it was written.
Where a subscription stands: plan, price, renewal, and what happens next.
"What happens next" is the point. Every state here answers a different question — when am I charged, when do I lose access, what do I owe — and a status badge alone answers none of them. A cancelled subscription that still has three weeks of access left is the case people most often get wrong.
Past-due is given the strongest treatment and its own action, because it is the only state where the customer can lose access to something they thought they had paid for, and it is usually fixed in one click.
A trial shows days remaining rather than the end date. "Ends 14 March" needs a calendar; "4 days left" does not.
Install
npx astralyx-ui add subscription-state
Usage
import { SubscriptionState } from '@/components/ui/subscription-state'
<SubscriptionState plan="Team" status="active" price={4900} renewsAt={renews} />Composer
Change the props and the source below follows.
Team — 12 seats
£49 / month
9 of 12 seats in use
Renews
<SubscriptionState
plan="Team"
status="active"
price={4900}
renewsAt={renews}
/>Examples
Active, past due and cancelling
<SubscriptionState plan="Team" status="active" renewsAt={renewsAt} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
SubscriptionState
Where a subscription stands: plan, price, renewal, and what happens next.
Extends Omit<ComponentProps<'section'>, 'children'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| plan | ReactNode | required | — |
| status | SubscriptionStatus | required | 'active' | 'trialing' | 'past_due' | 'canceled' | 'paused'. Each renders a different sentence, because each answers a different question. |
| price | number | — | Minor units per period. Whole amounts drop the decimals. |
| currency | string | 'USD' | — |
| locale | string | 'en-GB' | — |
| period | 'month' | 'year' | 'month' | — |
| renewsAt | Date | — | — |
| endsAt | Date | — | When access actually stops. For a cancelled subscription this is not the same as the renewal date, and conflating them is the bug. |
| trialEndsAt | Date | — | Rendered as days remaining. "Ends 14 March" needs a calendar; "4 days left" does not. |
| seats | { used: number; total: number } | — | — |
| actions | ReactNode | — | — |
| onFixPayment | () => void | — | — |
| seatsLabel | ReactNode | 'seats in use' | Follows the used-of-total seat count. |
| renewsLabel | ReactNode | 'Renews' | Precedes the renewal date. |
| fixPaymentLabel | ReactNode | 'Update payment method' | — |
Types
| Type | Definition | Description |
|---|---|---|
SubscriptionStatus | 'active' | 'trialing' | 'past_due' | 'canceled' | 'paused' | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| past_due | strongest treatment | The only state where a paying customer can lose access, and usually fixable in one click — so it gets a border and its own action. |