Template
Ursalane
A browse page for a public catalogue of machine-learning datasets. People narrow over a million entries by modality, size, format and type in a left facet panel, then scan compact cards showing owner/name, viewer availability, update time, size, downloads and likes.
Faceted dataset browser with card grid · App screen: table · Small tools and apps · front-end app (local state)
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.
- Source Sans 3Headings: Source Sans 3 700, 20px section title; count 18px 400 muted
- JetBrains MonoBody: Source Sans 3 400, 16px/1.5; card title in JetBrains Mono 15px 500; meta 14px
Patterns
- left facet panel with tabs
- chip filter groups
- range slider facet
- two-column result card grid
- result count beside title
- inline filter-by-name input
- sort dropdown
- stat row with icons per card
States it is designed for
- Loading skeleton cards
- No results with suggestion to remove last filter
- Very long owner/name truncates with full title in tooltip
- Error with retry
- Gated dataset badge on card
- Count abbreviations (1.2M, 853k) with exact value in title attr
Who it is for
- ML researchers looking for training or evaluation data
- Engineers comparing dataset formats and sizes
- Students exploring public corpora
Layout
- Public header: logo, global search, nav links, avatar
- Left facet panel (~300px, subtle grey): tabs (Main, Tasks, Libraries, Languages, Licenses, Other), Modalities chip group with coloured icons, Size range slider (<1K to >1T), Format chips, Type chips with tinted fills
- Main: title 'Datasets' + big muted count, filter-by-name input, Full-text search button, Sort dropdown
- Two-column grid of bordered cards: icon + owner/name (mono-ish), meta row (viewer, updated, rows, downloads, likes)
- Pagination at bottom
- Below 1024px: facets move into a 'Filters' sheet; grid becomes one column
Palette
open, technical, browsable. A library catalogue for data, not a store.
- page
#ffffff - facet panel
#fafbfc - primary text
#111827 - muted text
#5b6472 - card border
#8b8f97 - chip border
#8b8f97 - benchmark chip
#fff7e6 - benchmark chip text
#b45309 - traces chip
#eef4ff - traces chip text
#2563eb - env chip
#e8f7ef - env chip text
#047857
Every checked pair, measured again
| Sample | Where | Ratio | Needs |
|---|---|---|---|
| Aa | body text on page | 17.74:1 | 4.5:1 |
| Aa | muted meta on page | 5.98:1 | 4.5:1 |
| Aa | muted on facet panel | 5.77:1 | 4.5:1 |
| Aa | benchmark chip text | 4.71:1 | 4.5:1 |
| Aa | traces chip text | 4.68:1 | 4.5:1 |
| Aa | environment chip text | 4.96:1 | 4.5:1 |
| card border | 3.24:1 | 3:1 | |
| chip border on panel | 3.13:1 | 3:1 | |
| focus ring (blue) on page | 5.17: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
- Source Sans 3 700, 20px section title; count 18px 400 muted
- Body
- Source Sans 3 400, 16px/1.5; card title in JetBrains Mono 15px 500; meta 14px
Humanist sans + mono for identifiers, similar to observed; facet group headings 14px 600.
Spacing and imagery
Dense but readable: cards 72px tall with 12px padding, 16px grid gap; chips 28px tall radius 8px; facet groups spaced 24px; container 1440px.
Small coloured square icons per modality chip; tiny line icons in card meta; no photos.
Components
- Facet tabs
- Modality chip group (toggle)
- Size range slider with labels
- Format chips
- Type chips (tinted)
- Result count
- Filter-by-name input
- Full-text search toggle
- Sort dropdown (Trending, Most downloads, Most likes, Recently updated)
- Dataset card
- Pagination
- Active filters bar with clear-all
Interactions
- Toggling a chip updates results and URL query immediately; pressed chips show filled state + check
- Range slider has two thumbs with keyboard steps
- Filter-by-name debounced 200ms
- Sort change keeps filters
- Card hover raises border colour; whole card is a link
Data
Dataset{id, owner, name, modalities[], size_bucket, formats[], type (benchmark|traces|environment|other), updated_at, rows, downloads, likes, viewer_available, gated}FacetCount{facet, value, count}
Guardrails
Experience
- Keep facet state in the URL so results are shareable
- Show the total count and update it as filters change
- Pressed chips must look pressed beyond colour (check icon/fill)
- Abbreviate big numbers but expose exact values
- Offer one-click 'Clear all' when any filter is active
Accessibility
- Chips are toggle buttons with aria-pressed
- Slider thumbs have aria-valuetext like 'up to 1 million rows'
- Result count lives in an aria-live region
- Cards are single links with owner/name + stats as description
- Facet panel sheet on mobile traps focus and returns it
Security
- Sanitise query params before using in API calls; whitelist facet values
- Escape user-supplied dataset names when rendering
- Gated datasets never expose download URLs in listing responses
- Rate-limit search API
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 **Ursalane**, a browse page for a public catalogue of about a million ML datasets. A left facet panel narrows by modality, size, format and type; a two-column grid of compact cards shows owner/name and key stats. Everything is URL-driven so a filtered view can be shared.
### Stack
Next.js (App Router) + React 18 + TypeScript, Tailwind CSS, shadcn/ui (Radix Slider, ToggleGroup, Select, Tabs), lucide-react, TanStack Query for client refetches, date-fns for relative times. Mock data from a local JSON of ~2,000 invented datasets with a search function; swap to an API later.
### Pages & layout
1. **Header**: logo, global search, nav (Models, Datasets active, Demos, Docs, Pricing), avatar.
2. **/datasets** desktop grid `300px 1fr`:
- Facet panel: tabs Main/Tasks/Libraries/Languages/Licenses/Other; Modalities (3D, Audio, Document, Geospatial, Image, Tabular, Text, Time-series, Video) as chips with small coloured squares; Size (rows) dual slider <1K to >1T; Format chips (json, csv, parquet, imagefolder, audiofolder, webdataset, text, arrow-style columnar); Type chips (Benchmark amber, Traces blue, Environment green).
- Main: 'Datasets' + muted count '1,056,961'; filter-by-name input; 'Full-text search' toggle; Sort select. Two-column card grid; pagination.
3. Active filter bar above the grid with removable chips and Clear all.
4. Mobile: 'Filters (3)' button opens a sheet; one-column cards.
### Design system
- Colors: `--bg: #ffffff` (page), `--panel: #fafbfc` (facet panel), `--fg: #111827` (primary text), `--muted: #5b6472` (muted text), `--border: #8b8f97` (card border), `--chip-border: #8b8f97` (chip border), `--amber-bg: #fff7e6` (benchmark chip), `--amber: #b45309` (benchmark chip text), `--blue-bg: #eef4ff` (traces chip), `--blue: #2563eb` (traces chip text), `--green-bg: #e8f7ef` (env chip), `--green: #047857` (env chip text).
- Fonts: Source Sans 3 400/600/700; JetBrains Mono 500 15px for owner/name; meta 14px; body 16px/1.5.
- Spacing: 4px base; grid gap 16px; card padding 12px/16px.
- Radius: cards 10px, chips 8px, inputs 8px.
- Shadows: none; cards `1px solid` border at 40% `--border`, full strength on hover.
- Motion: 120ms border; results fade 150ms on change; none under reduced motion.
### Components & interactions
`FacetTabs`, `ChipGroup` (ToggleGroup type=multiple, check icon when pressed), `RangeFacet` (Radix Slider, log scale buckets), `ActiveFilters`, `ResultHeader` (count aria-live), `NameFilter`, `SortSelect`, `DatasetCard` (icon, owner/name, meta row: viewer, updated relative, rows, downloads, likes; gated badge), `Pagination`, `FiltersSheet`.
### Data & state
`Dataset{id, owner, name, modalities[], size_bucket, formats[], type, updated_at, rows, downloads, likes, viewer_available, gated}`. Filtering and sorting in a pure function over the mock array (memoised); facet counts computed alongside. URL is the source of truth (`?modality=text&format=parquet&size=1k-1m&sort=trending`). Numbers formatted with Intl compact notation.
### Accessibility
Chips `aria-pressed`, groups labelled by their heading. Slider `aria-valuetext`. Count updates announced politely. Cards are links; stats have visually hidden labels ('downloads 853 thousand'). Tinted type chips all meet 4.5:1 text. Focus ring 2px blue.
Verified contrast: body text on page: #111827 on #ffffff = 17.74:1; muted meta on page: #5b6472 on #ffffff = 5.98:1; muted on facet panel: #5b6472 on #fafbfc = 5.77:1; benchmark chip text: #b45309 on #fff7e6 = 4.71:1; traces chip text: #2563eb on #eef4ff = 4.68:1; environment chip text: #047857 on #e8f7ef = 4.96:1; card border: #8b8f97 on #ffffff = 3.24:1; chip border on panel: #8b8f97 on #fafbfc = 3.13:1; focus ring: #2563eb on #ffffff = 5.17:1.
### Security
Whitelist facet params; ignore unknown values. Render names as text (no HTML). When a real API arrives: rate-limit search, never include download links for gated datasets in listings.
### Performance & SEO
Server-render the first page for SEO with canonical URLs per common facet; client refetch afterwards. Virtualise nothing (paginate 30 per page). Title and meta description reflect active filters.
### Guardrails
- Invent every owner and dataset name.
- Don't reproduce any real catalogue's logo or icons.
- Keep chip colours for type only; modalities use neutral chips with small icons.
Acceptance criteria:
- [ ] Filters, sort and page persist in the URL
- [ ] Count and results update on each change
- [ ] Chips and slider are keyboard operable
- [ ] Empty-result state offers removing the last filter
- [ ] Contrast pairs pass