Template
Orvella
Inside an online-store admin, a merchant picks a paid tier from the Plans settings page and confirms it in a modal. The modal restates the plan and cycle price, captures a card and makes the recurring-charge consent explicit before one full-width pay button.
Plan change payment modal over pricing tiers · App screen: checkout · Small tools and apps · full-stack app (auth + DB)
A mock-up of the screen, drawn from its layout, palette and typefaces. A build follows the full prompt below.
Start from this screenRead the build prompt
Typefaces
The catalog's own faces. A screen composed into a template is drawn in that template's typefaces.
- ManropeHeadings: Manrope 700 - 24px modal title, 22px price line, -0.01em
- ManropeBody: Manrope 400/500 16px / 1.5
Patterns
- modal over pricing-tier cards
- billing cycle radio with savings badge
- inline card field
- price restatement before pay
- consent microcopy with inline links
- full-width pay button with lock icon
- settings left nav with active pill
States it is designed for
- dialog for upgrade vs downgrade (downgrade shows what features are lost and no charge today)
- card incomplete / declined
- processing payment
- success: dialog swaps to a check icon and 'Plan updated' with effective date, then plan cards refresh
- proration notice when switching mid-cycle
- error from provider with retry
- store owner only - staff see disabled select buttons with tooltip
Who it is for
- small online-store owners
- merchants upgrading from a free plan
Layout
- Top bar: wordmark left, notification icon and avatar with name right
- Settings shell: store identity (name + storefront URL with external-link icon) above a left nav (general, staff, plans active, billing, brand, domain, taxes, locations, shipping, payments, integrations, security)
- Main: back button and a row of four plan cards (Free, Economy, Business, Premium) with price, per-month note and select button
- Modal (~420px) centered over a 40% black scrim: close button in a grey header strip, title and subtitle, cycle radio (Yearly with green 'save' badge, Monthly), card field, plan name and price per month billed annually, payment-provider badge, consent microcopy with links, full-width Pay button showing total
- Mobile: plan cards stack; modal becomes a full-screen sheet with the pay button pinned
Palette
Friendly, clear and commercial; violet reads as the brand action colour, green only signals savings.
- page
#ffffff - app surface
#f8f8f8 - text
#1b1a22 - muted text
#6b6880 - primary violet
#5b3fd9 - on primary
#ffffff - savings badge bg
#d9f5d6 - savings badge text
#1e6b2e - input border
#8d8a9c - nav active pill
#e6e3f5 - link
#5b3fd9 - focus ring
#5b3fd9
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | body text on white | 17.25:1 | 4.5:1 |
| Aa | muted text on white | 5.36:1 | 4.5:1 |
| Aa | pay label on violet | 6.65:1 | 4.5:1 |
| Aa | savings badge text | 5.62:1 | 4.5:1 |
| input border on white | 3.36:1 | 3:1 | |
| Aa | active nav text on pill | 5.28:1 | 4.5:1 |
| Aa | link text on white | 6.65:1 | 4.5:1 |
| focus ring on white | 6.65:1 | 3:1 |
As vibld’s tokens
The palette on the fifteen colour tokens vibld styles a project with, each text colour on the fill it is read on. Marked tokens are solved from the palette, because no swatch held that role at 4.5:1.
- background
- card
- muted
- primary
- secondary
- accent
- destructive *
Type scale
- Display
- Manrope 700 - 24px modal title, 22px price line, -0.01em
- Body
- Manrope 400/500 16px / 1.5
Similar to the observed geometric grotesk; prices use tabular numerals; consent microcopy stays 16px in the muted colour; the provider badge caption is 12px minimum.
Spacing and imagery
Comfortable; 4px base, modal padding 24px, 16-20px between groups; radius 6px inputs and buttons, 10px modal and plan cards; subtle shadow 0 12px 40px rgba(20,18,40,.2) on the modal only.
None beyond a small card glyph in the field and a lock icon on the pay button; plan cards are text-only.
Components
- SettingsNav
- PlanCard (name, blurb, price, cycle note, select button, current-plan state)
- ChangePlanDialog
- CycleRadioGroup with SavingsBadge
- CardField (hosted element)
- PriceRestatement
- ProviderBadge
- ConsentText with links to terms and cancellation
- PayButton with lock icon and computed total
Interactions
- Selecting a plan card opens the dialog preloaded with that tier and the page-level cycle
- Toggling Yearly/Monthly updates the price line and the Pay button total instantly (yearly shows 12x monthly-equivalent)
- Pay is disabled until the card field is complete; pressing it shows a spinner and locks the dialog
- Close (X) or Escape asks for confirmation only if card input has started
- Current plan card shows a disabled 'Current plan' button
Data
Store{id, name, storefront_url, plan_id, cycle (monthly|yearly)}Plan{id, name (free|economy|business|premium), monthly_price, yearly_price, features[]}Subscription{id, store_id, plan_id, cycle, status (active|past_due|canceled), renews_at, provider_ref}StaffMember{user_id, store_id, role (owner|admin|staff)}
Guardrails
Experience
- Restate plan name, cycle and effective monthly price directly above the pay button
- Make the charge amount part of the button label
- Show recurring-charge consent in plain words next to the button, with a link to cancel
- Highlight savings with a small green badge only on the yearly option
- Downgrades never ask for a card
Accessibility
- The cycle choice is a radiogroup with labels that include the price ('Yearly, 204 per year')
- The savings badge text is part of the radio's accessible name, not colour-only
- Dialog traps focus and returns it to the plan card button that opened it
- Consent links are underlined and open in the same tab with a return path
- Pay button's accessible name includes the amount
Security
- Recompute price and plan server-side; ignore client-submitted amounts
- Only store owners can change plans (RLS and function check)
- Verify webhook signatures before updating subscriptions
- Record plan changes in an audit log with old and new plan
- Never store card data; keep the provider secret key in function secrets only
Build prompt
The baseline every prompt in the catalog assumes, then this design’s own ten sections, from goal to guardrails.
The baseline
### How to use these prompts Paste an entry's build prompt into your coding agent as the first message. Each prompt names its own stack, tokens and acceptance criteria; the rules below apply to all of them and can be prepended once per project. ### Engineering baseline - TypeScript strict mode, no `any`, small typed components, feature folders, and one source of truth for design tokens (CSS variables consumed by Tailwind). - Validate every input with a shared zod schema on the client and again on the server or edge function. Never trust client-side checks alone. - Show loading, empty and error states for every async view. Surface errors in plain language with a retry, and log details to the console in development only. - Keep secrets out of the bundle. Only publishable keys (for example a Supabase anon key) belong in client code; service-role keys, API keys and webhooks live in server or edge-function environment variables. ### Data and auth baseline (full-stack entries) - Enable Row Level Security on every table before inserting data. Default-deny, then add owner-scoped policies (`auth.uid() = user_id`) and explicit role checks for admin views. - Store roles in a separate table checked by a security-definer function, never in a user-editable profile field. - Upload files to private storage buckets with size and MIME limits, and serve them through signed URLs. - Rate-limit public endpoints (forms, auth, AI calls) and add a honeypot field or captcha to anonymous forms. - Take payments through a hosted checkout and verify webhooks by signature. Never handle raw card data. ### Accessibility and UX baseline - Target WCAG 2.2 AA: 4.5:1 contrast for normal text and 3:1 for large text, input borders, focus rings and meaningful icons or chart lines. Every palette in this catalog lists its verified pairs; re-check with a contrast tool after any colour change. - Keep body text at 16px or larger with 1.5 line height, nothing below 12px, no light weights under 24px, and uppercase only for short labels. - Give every interactive element a visible focus ring, full keyboard support, semantic landmarks, labelled form fields, and alt text on meaningful images. - Respect `prefers-reduced-motion` for every animation. Give drag-and-drop and carousels keyboard and button alternatives. - Build mobile-first and test at 375px, 768px and 1280px. ### Content guardrails - Use original copy, fictional sample data and placeholder or licensed imagery. Do not reuse another product's name, logo, screenshots or marketing text. - Label demo testimonials and metrics as samples. Collect the minimum personal data the feature needs. ### SaaS screen baseline - Design every screen for its full set of states: first-run empty, loading skeleton, partial data, error with retry, permission-denied, and success feedback. Each entry lists the states its screen needs. - Keep destructive actions (delete, revoke, downgrade, remove member) behind a confirmation that names the object, and prefer undo over a second dialog where the action is reversible. - Enforce authorisation on the server for every action a screen exposes. Hiding a button is not access control; check the role again in the API or RLS policy. - Never show secrets (API keys, tokens) in full after creation. Show them once, then mask them, and offer rotate and revoke. - Keep the app shell (navigation, workspace switcher, account menu) consistent across screens, and preserve filters, sort and scroll position when the user navigates back.
### Goal
Build **Orvella**, the Plans settings page of a store admin with its change-plan payment modal. Use a fictional store and four invented tiers; implement upgrade, downgrade, cycle switch and every payment state.
### Stack
Use React 18, TypeScript, Vite, Tailwind CSS, shadcn/ui, Radix, lucide-react, TanStack Query, react-hook-form, zod, Supabase. Payments use a hosted checkout provider's subscription API from an Edge Function. Use Supabase for Auth, Postgres (row-level security on every table) and Storage where noted; keep only the anon key in the browser and run privileged work in Edge Functions.
### Pages & layout
1. **/settings/plans**: plan cards plus the modal.
2. **/settings/billing**: invoices list and current subscription summary (stub is fine).
3. Other settings nav items route to simple placeholder pages so the shell is navigable.
Regions, in order:
- Top bar: wordmark left, notification icon and avatar with name right
- Settings shell: store identity (name + storefront URL with external-link icon) above a left nav (general, staff, plans active, billing, brand, domain, taxes, locations, shipping, payments, integrations, security)
- Main: back button and a row of four plan cards (Free, Economy, Business, Premium) with price, per-month note and select button
- Modal (~420px) centered over a 40% black scrim: close button in a grey header strip, title and subtitle, cycle radio (Yearly with green 'save' badge, Monthly), card field, plan name and price per month billed annually, payment-provider badge, consent microcopy with links, full-width Pay button showing total
- Mobile: plan cards stack; modal becomes a full-screen sheet with the pay button pinned
### Design system
- Colors: `--page: #ffffff` (page), `--app-surface: #f8f8f8` (app surface), `--text: #1b1a22` (text), `--muted-text: #6b6880` (muted text), `--primary-violet: #5b3fd9` (primary violet), `--on-primary: #ffffff` (on primary), `--savings-badge-bg: #d9f5d6` (savings badge bg), `--savings-badge-text: #1e6b2e` (savings badge text), `--input-border: #8d8a9c` (input border), `--nav-active-pill: #e6e3f5` (nav active pill), `--link: #5b3fd9` (link), `--focus-ring: #5b3fd9` (focus ring).
- Fonts: Manrope 700 - 24px modal title, 22px price line, -0.01em for headings; Manrope 400/500 16px / 1.5 for body. Similar to the observed geometric grotesk; prices use tabular numerals; consent microcopy stays 16px in the muted colour; the provider badge caption is 12px minimum.
- Spacing, radius and shadows: Comfortable; 4px base, modal padding 24px, 16-20px between groups; radius 6px inputs and buttons, 10px modal and plan cards; subtle shadow 0 12px 40px rgba(20,18,40,.2) on the modal only.
- Motion: 150-200 ms ease-out for hover, focus and overlay transitions; overlays fade and scale from 98% to 100%; everything collapses to an instant change under prefers-reduced-motion.
- Mood: Friendly, clear and commercial; violet reads as the brand action colour, green only signals savings. Imagery: None beyond a small card glyph in the field and a lock icon on the pay button; plan cards are text-only.
### Components & interactions
Build these components: SettingsNav; PlanCard (name, blurb, price, cycle note, select button, current-plan state); ChangePlanDialog; CycleRadioGroup with SavingsBadge; CardField (hosted element); PriceRestatement; ProviderBadge; ConsentText with links to terms and cancellation; PayButton with lock icon and computed total.
- Selecting a plan card opens the dialog preloaded with that tier and the page-level cycle
- Toggling Yearly/Monthly updates the price line and the Pay button total instantly (yearly shows 12x monthly-equivalent)
- Pay is disabled until the card field is complete; pressing it shows a spinner and locks the dialog
- Close (X) or Escape asks for confirmation only if card input has started
- Current plan card shows a disabled 'Current plan' button
### Data & state
Model: `Store{id, name, storefront_url, plan_id, cycle (monthly|yearly)}`; `Plan{id, name (free|economy|business|premium), monthly_price, yearly_price, features[]}`; `Subscription{id, store_id, plan_id, cycle, status (active|past_due|canceled), renews_at, provider_ref}`; `StaffMember{user_id, store_id, role (owner|admin|staff)}`.
Plans are seeded rows; the dialog computes display prices from them, but the Edge Function recomputes and creates the subscription. Subscription status comes back via a verified provider webhook and is pushed to the client by refetching on focus.
States to implement and demo:
- dialog for upgrade vs downgrade (downgrade shows what features are lost and no charge today)
- card incomplete / declined
- processing payment
- success: dialog swaps to a check icon and 'Plan updated' with effective date, then plan cards refresh
- proration notice when switching mid-cycle
- error from provider with retry
- store owner only - staff see disabled select buttons with tooltip
### Accessibility
- The cycle choice is a radiogroup with labels that include the price ('Yearly, 204 per year')
- The savings badge text is part of the radio's accessible name, not colour-only
- Dialog traps focus and returns it to the plan card button that opened it
- Consent links are underlined and open in the same tab with a return path
- Pay button's accessible name includes the amount
- Body text is 16px with line-height 1.5 (15px only inside dense tables), nothing renders below 12px, weights of 300 or lighter appear only at 24px and above, and uppercase is limited to short labels with at least 0.05em tracking.
Verified contrast: body text on white: #1b1a22 on #ffffff = 17.25:1; muted text on white: #6b6880 on #ffffff = 5.36:1; pay label on violet: #ffffff on #5b3fd9 = 6.65:1; savings badge text: #1e6b2e on #d9f5d6 = 5.62:1; input border on white: #8d8a9c on #ffffff = 3.36:1; active nav text on pill: #5b3fd9 on #e6e3f5 = 5.28:1; link text on white: #5b3fd9 on #ffffff = 6.65:1; focus ring on white: #5b3fd9 on #ffffff = 6.65:1.
### Security
- Recompute price and plan server-side; ignore client-submitted amounts
- Only store owners can change plans (RLS and function check)
- Verify webhook signatures before updating subscriptions
- Record plan changes in an audit log with old and new plan
- Never store card data; keep the provider secret key in function secrets only
RLS: `stores` - staff select their store, owner update; `plans` - public select; `subscriptions` - select for staff of the store, writes only via service role in the webhook function; `staff_members` - select within own store.
### Performance & SEO
Settings are an authenticated SPA route set; lazy-load the provider element when the dialog opens. Noindex all admin routes. Prefetch plans on hover of the Plans nav link.
### Guardrails
- Restate plan name, cycle and effective monthly price directly above the pay button
- Make the charge amount part of the button label
- Show recurring-charge consent in plain words next to the button, with a link to cancel
- Highlight savings with a small green badge only on the yearly option
- Downgrades never ask for a card
- Use the product name Orvella and fresh, generic copy throughout; all people, companies, amounts and IDs are invented, and no third-party brand, logo or wordmark appears.
- Keep components small and typed (no `any`), and surface every failure visibly instead of swallowing it.
Acceptance criteria:
- [ ] Upgrade, downgrade and cycle switch all work against mock provider responses
- [ ] Totals in dialog and button always match
- [ ] Staff users cannot trigger plan changes
- [ ] Modal is usable at 390px with the pay button visible
- [ ] All listed contrast pairs pass