Skip to main content
vibld

Template

Obsidra

A full-page, distraction-free checkout that upgrades a whole team workspace to a paid business plan. It collects organisation, location and billing contact details on the left while a sticky summary on the right shows who is upgrading, seats, next renewal, tax, discount and the amount due today.

Dark two-column workspace plan checkout · 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.

  • LatoHeadings: Lato 800, 24px plan title and 18px section heads, -0.01em
  • LatoBody: Lato 400 16px / 1.5 for labels, inputs and helper text (helper in the muted colour)

Patterns

  • two-column checkout with sticky order summary
  • grouped form sections with subheads
  • billing-cycle choice as large radio buttons
  • inline discount code field with apply button
  • accent top rule on summary card
  • focused chrome-less checkout (back link + centered wordmark only)
  • dark mode first

States it is designed for

  • pristine form with prefilled organisation name and billing email
  • field-level validation errors (required, invalid email, VAT format mismatch for the chosen country)
  • tax calculating skeleton on summary rows after country change
  • discount applied / discount invalid / discount expired
  • submitting: button spinner, all inputs disabled
  • payment declined error banner with retry and change-method action
  • 3-D Secure challenge in a modal handled by the hosted checkout provider
  • success: redirect to a confirmation page with receipt link
  • seat count changed by another admin while open (stale total warning)
  • network offline banner

Who it is for

  • workspace owners upgrading a team
  • finance or ops admins entering billing details
  • small-business teams in regions with VAT

Layout

  1. Top bar: back link with circled arrow at left, centered wordmark, nothing else (no app nav)
  2. Main two-column grid, max 920px: form card (~60%) left, summary card (~40%) right with a 4px violet top rule; summary becomes sticky at top 24px on desktop
  3. Form card sections: plan title, Your location (organisation name, country select, street address), Your company (billing email with helper text, optional VAT number with info tooltip and format hint), Choose your billing cycle (two large radio buttons), payment method block, terms line
  4. Summary card: upgrader identity chip with avatar and note that every member gains the plan, Review order button, line items (plan with seats, next renewal date, tax %, discount input + Apply), divider, bold Due today total
  5. Below 860px: summary moves above the form as a collapsible 'Order summary - EUR 96.80' bar, then the form; submit button becomes sticky at the bottom

Palette

Dark, calm, premium and low-noise; the page feels like a trusted payment step inside a creative tool.

  • canvas#000000
  • card surface#111113
  • raised surface#1a191f
  • text#f5f5f7
  • muted text#857f9a
  • input border#615f6b
  • primary violet#7857ff
  • on primary#ffffff
  • focus ring#a996ff
  • danger#ff6b6b
  • success#4ade80

Every checked pair, measured again

SampleWhereRatioNeeds
Aabody text on card17.32:14.5:1
Aamuted helper text on card4.94:14.5:1
Aabutton label on primary violet4.56:14.5:1
input border on card3.02:13:1
focus ring on card7.63:13:1
Aaerror text on card6.80:14.5:1
success tick on raised surface10.02:13:1
Aamuted text on raised summary panel4.57:14.5: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.

  • background
  • card
  • muted
  • primary
  • secondary
  • accent
  • destructive

Type scale

Display
Lato 800, 24px plan title and 18px section heads, -0.01em
Body
Lato 400 16px / 1.5 for labels, inputs and helper text (helper in the muted colour)

Similar to the observed humanist sans. Totals use tabular numerals; Due today is 20px 800.

Spacing and imagery

Comfortable density on a 4px base; card padding 24-32px, 20px between fields, 40px between sections; container 920px with 32px gutter; radius 8px on cards, 6px on inputs and buttons; no drop shadows - separation comes from the #111113 card on pure black and a 1px #1f1e24 hairline.

No imagery apart from a small round avatar in the summary; icons are 16px outline (info, chevron, check).

Components

  • BackLink
  • CheckoutFormCard with FormSection
  • CountrySelect (searchable combobox)
  • AddressField
  • BillingEmailField with helper text
  • VatNumberField with InfoTooltip and country-prefix hint
  • BillingCycleRadioGroup rendered as two large radio buttons with price delta
  • PaymentMethodField (hosted card element)
  • OrderSummaryCard with UpgraderChip
  • LineItem rows
  • DiscountCodeInput + Apply button
  • DueTodayTotal
  • SubmitBar (sticky on mobile)

Interactions

  • Switching billing cycle recalculates line items, tax and Due today with a 150ms number cross-fade; yearly shows a savings note
  • Country change updates tax rate, VAT hint prefix and address field order
  • Discount Apply shows a spinner inside the button, then either a green removable chip with the discount line or an inline error under the field
  • Review order scrolls to and focuses the first invalid field if the form is incomplete, otherwise opens a confirm step
  • Inputs show a 2px #a996ff ring on focus; hover lightens borders slightly
  • Summary stays sticky on desktop; on mobile it expands/collapses with a chevron and aria-expanded

Data

  • Workspace{id, name, seats, plan (free|business), owner_id}
  • BillingProfile{workspace_id, org_name, country, address_line, billing_email, vat_number?}
  • CheckoutSession{id, workspace_id, cycle (monthly|yearly), subtotal, tax_rate, discount_code?, discount_amount, total, currency, status (open|paid|failed)}
  • Coupon{code, percent_off|amount_off, expires_at}

Guardrails

Experience

  • Keep chrome minimal: no app sidebar or notifications on checkout, only a back link
  • Show the full breakdown (seats, renewal date, tax, discount) before the pay button, never after
  • Default to the cycle the user chose on the pricing page and preserve it on back navigation
  • Mark optional fields as optional rather than marking required ones with asterisks
  • Make the submit button label carry the amount ('Pay EUR 96.80')
  • Explain who is affected by the upgrade (every workspace member) next to the total

Accessibility

  • Every input has a visible label; helper and error text are linked with aria-describedby, and errors are announced via a polite live region
  • The billing-cycle choice is a real radiogroup with arrow-key navigation, not two buttons
  • The Due today total is announced when it changes (aria-live polite, debounced)
  • The info tooltip for VAT opens on focus as well as hover and is dismissible with Escape
  • Keep a 2px focus ring in #a996ff with a 2px offset against the black canvas
  • The collapsible mobile summary uses a button with aria-expanded and aria-controls

Security

  • Never collect raw card data: mount the hosted checkout provider's card element and confirm payment server-side
  • Recompute price, tax and discount in an Edge Function; the client total is display-only
  • Validate billing email, VAT number and address with zod on client and server; rate-limit discount-code checks per workspace to stop brute forcing
  • Only workspace owners and billing admins may open checkout (role check in RLS and in the Edge Function)
  • Verify provider webhooks by signature before marking a session paid, and write an audit-log row for every plan change

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 **Obsidra**, the dark checkout step a workspace owner sees when upgrading their team to the Business plan. Deliver the checkout screen (plus a minimal back link and centered wordmark) with realistic mock data - a fictional design studio with 5 seats in Spain - and every state from pristine to paid.

### Stack
Use React 18, TypeScript, Vite, Tailwind CSS, shadcn/ui, Radix, lucide-react, TanStack Query, react-hook-form, zod, Supabase, date-fns. Payments go through a hosted checkout provider's embeddable card element; the app never touches raw card numbers. Tax is computed server-side in 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. **/checkout/business**: two-column checkout described below, reached from an Upgrade button elsewhere in the app.
2. **/checkout/success**: short confirmation with plan name, amount paid, next renewal date and a link back to the workspace.
3. **/checkout/failed**: fallback page if the provider redirect returns an error, with a retry button.

Regions, in order:
- Top bar: back link with circled arrow at left, centered wordmark, nothing else (no app nav)
- Main two-column grid, max 920px: form card (~60%) left, summary card (~40%) right with a 4px violet top rule; summary becomes sticky at top 24px on desktop
- Form card sections: plan title, Your location (organisation name, country select, street address), Your company (billing email with helper text, optional VAT number with info tooltip and format hint), Choose your billing cycle (two large radio buttons), payment method block, terms line
- Summary card: upgrader identity chip with avatar and note that every member gains the plan, Review order button, line items (plan with seats, next renewal date, tax %, discount input + Apply), divider, bold Due today total
- Below 860px: summary moves above the form as a collapsible 'Order summary - EUR 96.80' bar, then the form; submit button becomes sticky at the bottom

### Design system
- Colors: `--canvas: #000000` (canvas), `--card-surface: #111113` (card surface), `--raised-surface: #1a191f` (raised surface), `--text: #f5f5f7` (text), `--muted-text: #857f9a` (muted text), `--input-border: #615f6b` (input border), `--primary-violet: #7857ff` (primary violet), `--on-primary: #ffffff` (on primary), `--focus-ring: #a996ff` (focus ring), `--danger: #ff6b6b` (danger), `--success: #4ade80` (success).
- Fonts: Lato 800, 24px plan title and 18px section heads, -0.01em for headings; Lato 400 16px / 1.5 for labels, inputs and helper text (helper in the muted colour) for body. Similar to the observed humanist sans. Totals use tabular numerals; Due today is 20px 800.
- Spacing, radius and shadows: Comfortable density on a 4px base; card padding 24-32px, 20px between fields, 40px between sections; container 920px with 32px gutter; radius 8px on cards, 6px on inputs and buttons; no drop shadows - separation comes from the #111113 card on pure black and a 1px #1f1e24 hairline.
- 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: Dark, calm, premium and low-noise; the page feels like a trusted payment step inside a creative tool. Imagery: No imagery apart from a small round avatar in the summary; icons are 16px outline (info, chevron, check).

### Components & interactions
Build these components: BackLink; CheckoutFormCard with FormSection; CountrySelect (searchable combobox); AddressField; BillingEmailField with helper text; VatNumberField with InfoTooltip and country-prefix hint; BillingCycleRadioGroup rendered as two large radio buttons with price delta; PaymentMethodField (hosted card element); OrderSummaryCard with UpgraderChip; LineItem rows; DiscountCodeInput + Apply button; DueTodayTotal; SubmitBar (sticky on mobile).

- Switching billing cycle recalculates line items, tax and Due today with a 150ms number cross-fade; yearly shows a savings note
- Country change updates tax rate, VAT hint prefix and address field order
- Discount Apply shows a spinner inside the button, then either a green removable chip with the discount line or an inline error under the field
- Review order scrolls to and focuses the first invalid field if the form is incomplete, otherwise opens a confirm step
- Inputs show a 2px #a996ff ring on focus; hover lightens borders slightly
- Summary stays sticky on desktop; on mobile it expands/collapses with a chevron and aria-expanded

Keep the price maths in a pure `computeTotals({seats, cycle, taxRate, coupon})` function with unit tests, and render the same result in the summary and the submit button label.

### Data & state
Model: `Workspace{id, name, seats, plan (free|business), owner_id}`; `BillingProfile{workspace_id, org_name, country, address_line, billing_email, vat_number?}`; `CheckoutSession{id, workspace_id, cycle (monthly|yearly), subtotal, tax_rate, discount_code?, discount_amount, total, currency, status (open|paid|failed)}`; `Coupon{code, percent_off|amount_off, expires_at}`.

TanStack Query loads workspace, billing profile and a quote (`POST /quote` returns line items for the current form values, debounced 300ms). Form state lives in react-hook-form with a zod schema per country (VAT regex, postcode rules). The checkout session is created only on submit.

States to implement and demo:
- pristine form with prefilled organisation name and billing email
- field-level validation errors (required, invalid email, VAT format mismatch for the chosen country)
- tax calculating skeleton on summary rows after country change
- discount applied / discount invalid / discount expired
- submitting: button spinner, all inputs disabled
- payment declined error banner with retry and change-method action
- 3-D Secure challenge in a modal handled by the hosted checkout provider
- success: redirect to a confirmation page with receipt link
- seat count changed by another admin while open (stale total warning)
- network offline banner

### Accessibility
- Every input has a visible label; helper and error text are linked with aria-describedby, and errors are announced via a polite live region
- The billing-cycle choice is a real radiogroup with arrow-key navigation, not two buttons
- The Due today total is announced when it changes (aria-live polite, debounced)
- The info tooltip for VAT opens on focus as well as hover and is dismissible with Escape
- Keep a 2px focus ring in #a996ff with a 2px offset against the black canvas
- The collapsible mobile summary uses a button with aria-expanded and aria-controls
- 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 card: #f5f5f7 on #111113 = 17.32:1; muted helper text on card: #857f9a on #111113 = 4.94:1; button label on primary violet: #ffffff on #7857ff = 4.56:1; input border on card: #615f6b on #111113 = 3.02:1; focus ring on card: #a996ff on #111113 = 7.63:1; error text on card: #ff6b6b on #111113 = 6.80:1; success tick on raised surface: #4ade80 on #1a191f = 10.02:1; muted text on raised summary panel: #857f9a on #1a191f = 4.57:1.

### Security
- Never collect raw card data: mount the hosted checkout provider's card element and confirm payment server-side
- Recompute price, tax and discount in an Edge Function; the client total is display-only
- Validate billing email, VAT number and address with zod on client and server; rate-limit discount-code checks per workspace to stop brute forcing
- Only workspace owners and billing admins may open checkout (role check in RLS and in the Edge Function)
- Verify provider webhooks by signature before marking a session paid, and write an audit-log row for every plan change

RLS: `workspaces` - members select, owner update; `billing_profiles` - select/update only where the user is owner or billing admin of `workspace_id`; `checkout_sessions` - insert via Edge Function only (service role), select for owner/billing admin; `coupons` - no client access, read inside the Edge Function.

### Performance & SEO
Checkout is its own route chunk; preload the provider script only on this route. Keep the page under 150KB JS before the provider element. Mark checkout routes noindex and give them a plain title such as 'Checkout - Obsidra'. Debounce quote requests and cancel in-flight ones when inputs change.

### Guardrails
- Keep chrome minimal: no app sidebar or notifications on checkout, only a back link
- Show the full breakdown (seats, renewal date, tax, discount) before the pay button, never after
- Default to the cycle the user chose on the pricing page and preserve it on back navigation
- Mark optional fields as optional rather than marking required ones with asterisks
- Make the submit button label carry the amount ('Pay EUR 96.80')
- Explain who is affected by the upgrade (every workspace member) next to the total
- Use the product name Obsidra 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:
- [ ] Changing cycle, country or discount updates every total consistently
- [ ] Invalid fields block submit and focus the first error
- [ ] Declined payment shows a recoverable error without losing form data
- [ ] RLS prevents non-billing members from reading billing profiles
- [ ] Layout works at 390px with the summary collapsible and the pay button reachable
- [ ] All contrast pairs listed above pass

Open the builderAll templatesThis palette on its own