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.tsx

About

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

Active

9 of 12 seats in use

Renews

Props
tsx
<SubscriptionState
  plan="Team"
  status="active"
  price={4900}
  renewsAt={renews}
/>

Examples

Active, past due and cancelling

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'>.

PropTypeDefaultDescription
planReactNoderequired—
statusSubscriptionStatusrequired'active' | 'trialing' | 'past_due' | 'canceled' | 'paused'. Each renders a different sentence, because each answers a different question.
pricenumber—Minor units per period. Whole amounts drop the decimals.
currencystring'USD'—
localestring'en-GB'—
period'month' | 'year''month'—
renewsAtDate——
endsAtDate—When access actually stops. For a cancelled subscription this is not the same as the renewal date, and conflating them is the bug.
trialEndsAtDate—Rendered as days remaining. "Ends 14 March" needs a calendar; "4 days left" does not.
seats{ used: number; total: number }——
actionsReactNode——
onFixPayment() => void——
seatsLabelReactNode'seats in use'Follows the used-of-total seat count.
renewsLabelReactNode'Renews'Precedes the renewal date.
fixPaymentLabelReactNode'Update payment method'—

Types

TypeDefinitionDescription
SubscriptionStatus
'active' | 'trialing' | 'past_due' | 'canceled' | 'paused'
—

Behaviour

TopicDetailDescription
past_duestrongest treatmentThe only state where a paying customer can lose access, and usually fixable in one click — so it gets a border and its own action.