Skip to main content
vibld

Template

Nardell

In an API-key management platform, deleting an API (and every key under it) is irreversible. The dialog states exactly what will be lost and requires typing the API's name and a phrase containing the number of keys, while a separate delete-protection setting can block deletion entirely.

Double type-to-confirm delete API dialog · App screen: delete account · 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.

  • GeistHeadings: Geist 600 18px dialog and card titles
  • GeistBody: Geist 400 16px / 1.5

Patterns

  • type-to-confirm destructive dialog
  • two confirmation inputs (resource name + phrase with count)
  • danger button disabled until both match
  • danger zone cards at bottom of settings
  • delete protection toggle as a guard
  • copyable ID fields

States it is designed for

  • protection disabled / enabled
  • dialog pristine
  • partial match
  • both matched
  • deleting (spinner, inputs locked)
  • deleted (redirect + toast)
  • failed with reason
  • insufficient role (buttons hidden, read-only note)

Who it is for

  • developers operating API key infrastructure
  • platform admins

Layout

  1. Left sidebar: workspace switcher, APIs tree (current API with requests, keys, settings active), rate limit, authorization, audit log, logs, identities, settings; usage meter and account at bottom
  2. Settings content: stacked cards - API ID and keyspace ID rows with copy fields, name/other editable rows with Save, plan upsell card, Delete protection card with status badge and Enable button, Delete API card with red outline button
  3. Dialog (~360px): title + close; warning paragraph with bold lead; 'Type <name> to confirm' input; 'To verify, type <phrase>' input; full-width danger button (disabled until valid); small caution text
  4. Mobile: dialog full-width sheet; cards stack with actions below text

Palette

Sober and careful; red is used only on the delete path.

  • page#ffffff
  • app bg#fbfbfc
  • text#18181b
  • muted text#6b6b72
  • input border#8e8e96
  • danger#dc2626
  • on danger#ffffff
  • danger disabled#f4b3ab
  • warning badge bg#fef3c7
  • warning badge text#854d0e
  • primary blue#1d6fe0
  • focus ring#1d6fe0

Every checked pair, measured again

SampleWhereRatioNeeds
Aabody text on white17.72:14.5:1
Aamuted text on app bg5.11:14.5:1
AaDelete label on danger4.83:14.5:1
Aadanger outline button text4.83:14.5:1
input border on white3.25:13:1
Aadisabled badge text6.15:14.5:1
AaUpgrade label on blue4.77:14.5:1
focus ring on white4.77:13: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
Geist 600 18px dialog and card titles
Body
Geist 400 16px / 1.5

Geist Mono 15px for IDs; badge text uppercase 12px 600 with 0.06em tracking.

Spacing and imagery

Comfortable; 4px base, cards 20px padding with 1px border, 16px between cards, dialog 20px padding; radius 8px cards and inputs, 10px dialog; dialog shadow 0 16px 40px rgba(0,0,0,.14).

None; small copy and key icons.

Components

  • ApiSidebar tree
  • CopyField
  • SettingsCard
  • DeleteProtectionCard with StatusBadge
  • DangerCard
  • DeleteApiDialog
  • ConfirmInput (exact match)
  • DangerButton

Interactions

  • Delete API opens the dialog with focus on the first input
  • Each input shows a check when it matches exactly (case-sensitive name, phrase including key count)
  • Danger button enables only when both match; Enter submits when enabled
  • If delete protection is on, the Delete button is disabled with a tooltip linking to the protection card
  • After deletion, redirect to the APIs list with a toast

Data

  • Api{id, workspace_id, name, keyspace_id, delete_protection (bool), created_at}
  • Key{id, api_id, prefix, hash, created_at, revoked_at?}
  • AuditEvent{id, workspace_id, actor_id, action, target_id, at}

Guardrails

Experience

  • State the blast radius (keys, data, analytics) in one paragraph
  • Make the confirmation specific to this resource
  • Offer a protection switch that prevents accidents
  • Keep Cancel easy; the danger button is the only red fill
  • Redirect somewhere sensible after success

Accessibility

  • Dialog uses role=alertdialog with the warning as its description
  • Confirm instructions are part of each input's label so screen readers hear what to type
  • Match state is conveyed in text ('Matches') as well as an icon
  • Disabled danger button explains why via aria-describedby
  • Focus returns to the Delete button if cancelled

Security

  • Enforce delete protection and role checks server-side, not only in the UI
  • Re-verify the confirmation phrase server-side and require recent authentication
  • Delete keys and API in one transaction, and write an audit event
  • Rate-limit destructive endpoints
  • Soft-delete for 24h where possible, but copy must not promise recovery if none exists

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 **Nardell**, the settings page of an API inside a key-management platform, focused on the delete path: delete-protection card, danger card and a double type-to-confirm dialog. Use fictional API names and IDs.

### Stack
Use React 18, TypeScript, Vite, Tailwind CSS, shadcn/ui, Radix, lucide-react, TanStack Query, react-hook-form, zod, Supabase.  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. **/apis/:id/settings**: this page.
2. **/apis**: list to redirect to after deletion.
3. Keys and requests tabs as stubs.

Regions, in order:
- Left sidebar: workspace switcher, APIs tree (current API with requests, keys, settings active), rate limit, authorization, audit log, logs, identities, settings; usage meter and account at bottom
- Settings content: stacked cards - API ID and keyspace ID rows with copy fields, name/other editable rows with Save, plan upsell card, Delete protection card with status badge and Enable button, Delete API card with red outline button
- Dialog (~360px): title + close; warning paragraph with bold lead; 'Type <name> to confirm' input; 'To verify, type <phrase>' input; full-width danger button (disabled until valid); small caution text
- Mobile: dialog full-width sheet; cards stack with actions below text

### Design system
- Colors: `--page: #ffffff` (page), `--app-bg: #fbfbfc` (app bg), `--text: #18181b` (text), `--muted-text: #6b6b72` (muted text), `--input-border: #8e8e96` (input border), `--danger: #dc2626` (danger), `--on-danger: #ffffff` (on danger), `--danger-disabled: #f4b3ab` (danger disabled), `--warning-badge-bg: #fef3c7` (warning badge bg), `--warning-badge-text: #854d0e` (warning badge text), `--primary-blue: #1d6fe0` (primary blue), `--focus-ring: #1d6fe0` (focus ring).
- Fonts: Geist 600 18px dialog and card titles for headings; Geist 400 16px / 1.5 for body. Geist Mono 15px for IDs; badge text uppercase 12px 600 with 0.06em tracking.
- Spacing, radius and shadows: Comfortable; 4px base, cards 20px padding with 1px border, 16px between cards, dialog 20px padding; radius 8px cards and inputs, 10px dialog; dialog shadow 0 16px 40px rgba(0,0,0,.14).
- 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: Sober and careful; red is used only on the delete path. Imagery: None; small copy and key icons.

### Components & interactions
Build these components: ApiSidebar tree; CopyField; SettingsCard; DeleteProtectionCard with StatusBadge; DangerCard; DeleteApiDialog; ConfirmInput (exact match); DangerButton.

- Delete API opens the dialog with focus on the first input
- Each input shows a check when it matches exactly (case-sensitive name, phrase including key count)
- Danger button enables only when both match; Enter submits when enabled
- If delete protection is on, the Delete button is disabled with a tooltip linking to the protection card
- After deletion, redirect to the APIs list with a toast

### Data & state
Model: `Api{id, workspace_id, name, keyspace_id, delete_protection (bool), created_at}`; `Key{id, api_id, prefix, hash, created_at, revoked_at?}`; `AuditEvent{id, workspace_id, actor_id, action, target_id, at}`.

The confirm phrase is generated from the live key count (e.g. 'delete this api and 3 keys'); refetch the count when the dialog opens. Deletion runs in an Edge Function inside a transaction.

States to implement and demo:
- protection disabled / enabled
- dialog pristine
- partial match
- both matched
- deleting (spinner, inputs locked)
- deleted (redirect + toast)
- failed with reason
- insufficient role (buttons hidden, read-only note)

### Accessibility
- Dialog uses role=alertdialog with the warning as its description
- Confirm instructions are part of each input's label so screen readers hear what to type
- Match state is conveyed in text ('Matches') as well as an icon
- Disabled danger button explains why via aria-describedby
- Focus returns to the Delete button if cancelled
- 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: #18181b on #ffffff = 17.72:1; muted text on app bg: #6b6b72 on #fbfbfc = 5.11:1; Delete label on danger: #ffffff on #dc2626 = 4.83:1; danger outline button text: #dc2626 on #ffffff = 4.83:1; input border on white: #8e8e96 on #ffffff = 3.25:1; disabled badge text: #854d0e on #fef3c7 = 6.15:1; Upgrade label on blue: #ffffff on #1d6fe0 = 4.77:1; focus ring on white: #1d6fe0 on #ffffff = 4.77:1.

### Security
- Enforce delete protection and role checks server-side, not only in the UI
- Re-verify the confirmation phrase server-side and require recent authentication
- Delete keys and API in one transaction, and write an audit event
- Rate-limit destructive endpoints
- Soft-delete for 24h where possible, but copy must not promise recovery if none exists

RLS: `apis` - members select, admins update; delete only via Edge Function after checks; `keys` - members select metadata; `audit_events` - admins select, insert via service role.

### Performance & SEO
Small page; load the dialog lazily. Noindex.

### Guardrails
- State the blast radius (keys, data, analytics) in one paragraph
- Make the confirmation specific to this resource
- Offer a protection switch that prevents accidents
- Keep Cancel easy; the danger button is the only red fill
- Redirect somewhere sensible after success
- Use the product name Nardell 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:
- [ ] Danger button enables only on exact matches
- [ ] Protection blocks deletion server-side
- [ ] Audit event written
- [ ] Keyboard-only flow works
- [ ] 390px layout works

Open the builderAll templatesThis palette on its own