Template
Duesworth
A full-stack starter that adds sign-in, paid plans and entitlement checks to an app. Signed-in users manage their account, pick a plan (monthly, yearly or one-time), see their subscription and unlock gated content based on entitlements.
Subscription and entitlements account settings starter · App · Small tools and apps · full-stack app (auth + DB)
A mock-up of the homepage, drawn from this design’s layout, palette and typefaces. A build follows the full prompt below.
Add app screens
Pick up to 6 screens, such as a dashboard, settings or an empty state. Each is built in this design’s own palette and typefaces, with its states and guardrails.
Start from this templateRead the build prompt
Typefaces
Ubuntu Sans and its mono are a matched, utilitarian pair with a faint warmth that suits the quiet brown-vignetted settings screens.
- Ubuntu SansHeadings: panel titles 24px 600 at -0.02em, prices 20px 600
- Ubuntu SansBody: body 16px/1.5 400, smallest label 12px
- Ubuntu Sans MonoFigures and code: ids and dates in tables 14px
Patterns
- settings layout with left section list
- segmented control monthly/yearly/one-time
- compact plan card with full-width select button
- entitlement-gated content block
- dark app shell with warm vignette
- subscription status card
States it is designed for
- no plans configured (empty state with setup hint)
- loading plans skeleton
- checkout returned but entitlement not yet active (pending with auto-refresh)
- active / trialing / past due / cancelled-at-period-end subscription states
- locked content vs unlocked
- error fetching entitlements with retry
- signed-out redirect to login with return path
Who it is for
- SaaS developers adding paywalls
- teams needing plan-based feature flags
- solo builders shipping paid tools
Layout
- Top bar: small product label left, avatar menu right
- Settings shell: 200px left section list (Account, Pricing, Subscription, Paid content), main panel 640px max
- Pricing panel: title + subtitle, segmented control (Monthly / Yearly / One-time), plan cards in a responsive grid
- Subscription panel: current plan card with status, renewal date, manage/cancel actions
- Paid content panel: locked state with upgrade prompt or unlocked sample content
- Mobile: section list becomes a horizontal tab strip under the top bar
Palette
Quiet, warm-dark, utilitarian. A faint brown vignette softens an otherwise black settings screen.
- app background
#0a0a0a - warm radial vignette
#2a2220 - panel / card
#171413 - selected nav item / segmented thumb
#2e2a29 - primary text
#fafafa - secondary text
#a8a29e - primary button fill
#fafafa - primary button label
#171717 - card / input border
#6b6360 - active status
#4ade80 - trial / past due
#fbbf24
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | body text | 18.97:1 | 4.5:1 |
| Aa | text on panel | 17.56:1 | 4.5:1 |
| Aa | muted text on panel | 7.27:1 | 4.5:1 |
| Aa | primary button label | 17.18:1 | 4.5:1 |
| card / input border | 3.12:1 | 3:1 | |
| focus ring | 18.97:1 | 3:1 | |
| active status | 10.52:1 | 3:1 | |
| trial / past-due status | 10.98: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
- Ubuntu Sans 600, 24px/1.3 panel titles, tracking -0.02em
- Body
- Ubuntu Sans 400, 16px/1.5
Prices Ubuntu Sans 600 20px; Ubuntu Sans Mono 14px for ids and dates in tables; smallest label 12px.
Spacing and imagery
Compact app density; 8px base; panel padding 24px; card radius 8px, button radius 6px; hairline borders, no shadows.
No imagery; small lucide icons in the section list; a lock illustration made of a single icon for gated content.
Components
- SettingsNav
- SegmentedControl (three options)
- PlanCard
- SelectPlanButton
- SubscriptionCard
- StatusBadge
- EntitlementGate
- UpgradePrompt
- AvatarMenu
- ConfirmDialog
- Toast
Interactions
- Segmented control swaps visible plans instantly and keeps focus on the control
- Select plan opens hosted checkout; return URL lands on Subscription panel
- Cancel requires confirmation and shows the access end date
- Gated panel re-checks entitlement on focus/visibility change
- Section list highlights current section; keyboard arrows move between items
Data
Profile{id, email, name, created_at}Product{id, name, kind (subscription|one_time)}Price{id, product_id, interval (month|year|once), amount, currency}Subscription{id, user_id, price_id, status (trialing|active|past_due|canceled), current_period_end, cancel_at_period_end}Entitlement{id, user_id, key, source (subscription|purchase|grant), expires_at}
Guardrails
Experience
- Show the interval next to every price and never mix intervals in one view
- Explain what a plan unlocks in one line on the card
- Keep cancel reachable but secondary; always show when access ends
- Gate content server-side; the client only reflects the result
- After checkout, land the user on their subscription with a clear pending/active message
Accessibility
- Segmented control is a radiogroup with arrow keys
- Section list is a nav with aria-current
- Status badges include text
- Locked content explains why and offers a focusable upgrade link
- Dialogs trap focus and return it on close
- Focus ring 2px white with offset on dark
Security
- RLS: profiles, subscriptions and entitlements readable only where user_id = auth.uid(); no client writes
- Entitlement checks happen in server components/route handlers, never trusting client flags
- Webhook signature verification and idempotent upserts
- Checkout session created server-side from a known price id; rate-limited
- Magic-link redirects restricted to an allowlist
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 **Duesworth**, a starter that adds sign-in, paid plans and entitlement checks to any app. A signed-in user lands in a dark settings shell with four sections: Account, Pricing, Subscription and Paid content. They pick a monthly, yearly or one-time plan, pay through a hosted checkout, and immediately see gated content unlock. The point is correct, legible billing state, not marketing. ### Stack Next.js (App Router), React, TypeScript, Tailwind CSS, shadcn/ui (Radix), lucide-react, react-hook-form + zod, TanStack Query, date-fns. Supabase Auth (magic link + OAuth) and Postgres; an Edge Function receives billing webhooks and writes subscriptions and entitlements. Use a hosted checkout provider and its customer portal. ### Pages & layout 1. **/login**: centered card, email field, magic-link button, one OAuth button. 2. **/settings/account**: name and email form, sign-out. 3. **/settings/pricing**: title, subtitle, segmented control (Monthly / Yearly / One-time), plan cards (name, price with interval, one-line benefit, full-width "Select plan" button). 4. **/settings/subscription**: current plan card with status badge, renewal or end date, "Manage billing" (portal) and "Cancel" actions; purchase history table. 5. **/settings/paid**: entitlement-gated block showing either a lock with an upgrade prompt or sample premium content. Shell: top bar with product label and avatar menu; 200px left section list; main panel max 640px. Under 768px the list becomes a horizontal tab strip. ### Design system - Colors: `--canvas: #0a0a0a` (app background), `--vignette: #2a2220` (warm radial vignette), `--surface: #171413` (panel / card), `--surface-2: #2e2a29` (selected nav item / segmented thumb), `--fg: #fafafa` (primary text), `--muted: #a8a29e` (secondary text), `--btn: #fafafa` (primary button fill), `--btn-fg: #171717` (primary button label), `--border: #6b6360` (card / input border), `--success: #4ade80` (active status), `--warn: #fbbf24` (trial / past due). - Fonts: Ubuntu Sans (Google Fonts) 600 for panel titles at 24px with -0.02em tracking, 400 body 16px/1.5; Ubuntu Sans Mono 14px for dates and ids. - Spacing: 8px base, 24px panel padding, 16px card gap. - Radius: 8px cards, 6px buttons and inputs. - Shadows: none; background has a warm radial vignette of `#2a2220` centred behind the panel. - Motion: 150ms fades; skeletons pulse, disabled under reduced motion. ### Components & interactions SettingsNav, SegmentedControl (Radix ToggleGroup, single), PlanCard, SelectPlanButton (disabled with spinner while creating a checkout session), SubscriptionCard, StatusBadge (Active, Trialing, Past due, Ends on date), EntitlementGate (server component that renders children or UpgradePrompt), AvatarMenu, ConfirmDialog, Toast. After checkout the user returns to /settings/subscription?pending=1, which polls until the entitlement appears. ### Data & state Tables `profiles`, `products`, `prices`, `subscriptions(user_id, price_id, status, current_period_end, cancel_at_period_end)`, `purchases(user_id, price_id, amount, created_at)`, `entitlements(user_id, key, source, expires_at)`. A SQL function `has_entitlement(key)` returns a boolean for `auth.uid()`. Seed three invented plans across the three intervals. ### Accessibility Segmented control is a radiogroup; the section list uses `aria-current`. Status badges carry text. Locked content explains what unlocks it and offers a focusable link. Dialogs trap and restore focus. Focus ring 2px `#fafafa` with 2px offset. Verified contrast: body text: #fafafa on #0a0a0a = 18.97:1; text on panel: #fafafa on #171413 = 17.56:1; muted text on panel: #a8a29e on #171413 = 7.27:1; primary button label: #171717 on #fafafa = 17.18:1; card / input border: #6b6360 on #171413 = 3.12:1; focus ring: #fafafa on #0a0a0a = 18.97:1; active status: #4ade80 on #171413 = 10.52:1; trial / past-due status: #fbbf24 on #171413 = 10.98:1. ### Security - RLS on `profiles`, `subscriptions`, `purchases`, `entitlements`: select where `user_id = auth.uid()`; inserts and updates only from the service role in the webhook function. `products` and `prices` are public read. - Verify webhook signatures and upsert idempotently on the provider event id. - Gate premium routes on the server with `has_entitlement`; never rely on client state. - Create checkout sessions server-side from a known price id; rate-limit; restrict auth redirect URLs to an allowlist. ### Performance & SEO All settings routes are dynamic and noindex. Stream the plan list with a skeleton; cache prices for 5 minutes. Keep JS small by using server components for gated content. ### Guardrails - Invent plan names and amounts; no real brand names. - Never show "Active" before the webhook confirms it. - Acceptance criteria: (1) buying a plan unlocks /settings/paid without a manual refresh; (2) cancelling shows the end date and keeps access until then; (3) another user cannot read your purchases; (4) all three intervals render and keyboard-switch; (5) contrast pairs pass.