A trust-first, conversion-focused design system for thin.ly — a modern link platform that unifies short links, governed link controls, link-in-bio pages, and a portfolio/site builder into one cohesive product. Creators, professionals, and teams manage one governed presence everywhere.
Experience goals: create and publish in under 5 minutes · know link status and policy state at a glance · build a professional public presence without design expertise · feel safe sharing any generated URL.
This system was reverse-engineered from the product's own code. If you have access, explore these repositories to build higher-fidelity work:
- Frontend (React + Vite + Tailwind): https://github.com/systemu-net/onetime — the thin.ly web app: dashboard, governance, campaigns, QR, link-in-bio pages, portfolio/page editor, marketing site. The real design vocabulary lives here.
- Backend (Ruby on Rails): https://github.com/systemu-net/thin.ly
— URL shortener service, analytics API, QR generation, avatar/upload handling.
See
ANALYTICS_API.md,AVATAR_IMPLEMENTATION.md,AUTOMATIC_QR_CODE_GENERATION.md.
Key files referenced while authoring: tailwind.config.js, src/index.css,
src/pages/Dashboard.tsx, src/pages/GovernancePage.tsx,
src/components/governance/GovernanceStatsGrid.tsx, src/components/elements/button.tsx,
src/components/Header.tsx.
| Surface | What it is |
|---|---|
| Short links | Paste-and-shorten, branded slugs, QR codes, click analytics. |
| Link Governance | The trust layer — lifecycle states (active / paused / expired / draft), routing rules, threat detection, bulk control, deep analytics. |
| Campaigns | Group links, track aggregate performance, launch/sale/event types. |
| Link-in-bio | A single public page of links — the creator's hub. |
| Portfolio / Page builder | A richer public presence built without design skill, with platform guardrails preserving readability + trust cues. |
Everything shares one bento-grid visual language so short-link management, analytics, bio pages, and the portfolio builder feel like one product.
- Clear over clever — legible hierarchy, plain language, no decoration that competes with the data.
- Governed, not restrictive — show control and safety without blocking fast paths.
- Fast paths for common tasks — the quick-shortener is always one field away.
- Progressive disclosure — advanced routing/policy controls reveal on demand.
- One system, many publishing outcomes — links, pages, portfolios, QR — same kit.
Voice — confident, plain, a little technical. thin.ly speaks like a trustworthy power tool: short declarative phrases, verbs first, no hype.
- Person: Addresses the user as you ("Share your links to see activity here", "paste your long URL here…"). The product refers to itself by name, thin.ly, lowercase, never "we" in UI microcopy.
- Casing: Sentence case for body and buttons (Check it out, See all,
New Link). Mono UPPERCASE eyebrows label sections (
LIVE ACTIVITY,TOTAL LINKS) with wide letter-spacing. Display headings occasionally go ALL-CAPS for impact (LINK GOVERNANCE). - Mono "code comment" accent: A signature device — short status lines written
like source comments:
// instant · governed · safe. Used sparingly on dark panels and eyebrows. Middots·separate metadata. - Status language is explicit and reassuring: active, paused, expired, draft; traffic suspended, past expiry date, not yet active, all-time traffic, X% of portfolio live. Numbers always carry a unit or qualifier.
- Tone of empty/error states: helpful and calm — "Share your links to see activity here." · "No links match your filter." · "paste a URL first…".
- Emoji: Used, but disciplined and functional — as category/section markers (🔗 links, 📣 campaigns, ▦ QR, ◧ pages, ⚡ Featured, 👋 greeting). Never decorative spam. In a strict/enterprise context they can be swapped for the line icons.
- Microcopy length: Buttons 1–3 words. Eyebrows 1–2 words. Helper text one short sentence. Headlines under ~5 words.
Examples to emulate: Good morning, alex 👋 · Shorten on the fly ·
// instant · governed · safe · 73% of portfolio live · Check it out →.
Overall vibe: a warm, friendly bento dashboard — soft pastel tiles on an off-white canvas, confident dark display type, monospace accents that signal "governed / technical / safe." Calm, premium, trustworthy. Not corporate-cold, not playful-toy.
- Color: Warm ink-on-canvas neutrals (
--canvas-1: #f4f5f1,--ink-1: #15151b). A highlighter pastel palette (mint, coral, peach, lilac, sun, sky) colors the bento tiles — each hue has a base, soft tints (-2,-3) and an accessible deep variant (-d) for text/icons on its own tint. Violet#7c3aed→#a855f7is the brand/CTA accent (gradients on dark panels only). Forest#1f3a32is the deep surface (plan card, footer). A legacy teal (~#2dd4c4) tints the brand illustrations and feature icons. - Type: Display = Bricolage Grotesque (bold, tight
-0.025emtracking, sometimes uppercase). UI/body = Geist. Mono = Geist Mono for labels, slugs, metrics, and//comments. (Rubik/Poppins are legacy-marketing only.) - Backgrounds: Flat warm canvas — no busy gradients on light surfaces. Radial violet/mint glows appear only inside dark panels (quick-shortener, plan card) as soft pointer-events-none halos. No photographic backgrounds; brand illustrations are flat teal vector scenes.
- Cards ("bento"):
--radius-bento: 18px, soft layered shadow (--shadow-bento-sm→--shadow-bentoon hover), often a pastel fill. Stat tiles are white with a 3px top accent stripe in the status hue + a tinted icon tile top-right. Inner rows use--radius-sm: 10px. - Borders: Hairline
#e5e7e0("dashline"). Dark surfaces usergba(255,255,255,0.10). Selected/active states use violet at low alpha. - Shadows: Always soft and double-layered — a 1px ground line + a wide diffuse blur. Never hard or dark. Violet glow only on the primary CTA.
- Corner radii: Pills (
999px) for every button, badge, chip and input on bars; 18px bento cards; 10–14px inner elements; circular avatars/favicons. - Animation: Gentle and purposeful.
fadeIn(8px rise) on mount;waveon the greeting 👋;floaton decorative illustrations;pulse-liveon the live dot. Hover =translateY(-2px)+ shadow step-up. Eases are short (cubic-bezier(0.22,1,0.36,1), 0.15–0.3s). No bounces, no infinite spinners except true loading. - Hover states: Cards lift 2px and deepen shadow; list rows nudge
translateXand brighten to white; pill buttons darken (ink→ink-2) or raise the violet glow. - Press states: Subtle — overlay darken / slight settle; no aggressive shrink.
- Transparency & blur: Status/metric chips use the status hue at ~10% alpha over white. Glows are radial-gradient halos at low opacity inside dark panels. Backdrop blur is reserved for overlays/drawers.
- Layout rules: 12-column bento grid,
--gap-bento: 14px, max width 1480px. Page padding ~28px. Sticky top bar with search (⌘K) + primary CTA. Drawers slide from the right for detail/edit; modals center for create. - Imagery vibe: Flat vector, teal-forward, friendly — people-at-work scenes and simple line glyphs. Cool/clean, never photographic, never grainy.
- Primary icon set: Lucide (
react-icons/luin the codebase —LuLink2,LuCircleCheck,LuCirclePause,LuHourglass,LuMousePointerClick,LuFilePenLine,LuEllipsisVertical,LuPlay…). Clean 2px stroke, rounded joints — matches the calm, governed tone. Use Lucide for all UI icons. It is CDN-available (lucide/lucide-static); this system links Lucide from CDN in component cards and kits. - Marketing/feature icons: a small set of bespoke teal line illustrations
(brand-recognition, detailed-records, fully-customizable) — copied into
assets/icon-*.svg. Brand scenes (assets/illustration-*.svg) are flat teal vector art. Use these only in marketing surfaces, not in the app chrome. - Status iconography is paired with color, never color-only: active = check, paused = pause, expired = hourglass, draft = pen — each in its status hue.
- Emoji appear as lightweight category markers in the app (🔗 📣 ▦ ◧ ⚡ 👋). Treat as optional accents; the Lucide equivalents are the accessible fallback.
- Favicon/avatar: link favicons are pulled live via Google's favicon service and shown in circular frames; user avatars are circular.
- Do not hand-draw new icon SVGs — use Lucide, or copy an existing asset.
Foundations
styles.css— global entry (import this one file).tokens/colors.css·typography.css·spacing.css·effects.css·fonts.cssguidelines/*.card.html— foundation specimen cards (Design System tab).
Components (components/) — React primitives, each with .jsx + .d.ts +
.prompt.md + a @dsCard HTML:
core/— Button, Input, Switch, Tabs (controls).data/— StatusPill, Badge, Card, StatTile (trust & data display).
UI kit (ui_kits/thinly-app/) — high-fidelity product recreations:
index.html(interactive),Dashboard.jsx,GovernancePage.jsx,LinkInBio.jsx,AppShell.jsx.- Profile suite —
Profile.html(owner's own profile),Profile-public.html(the publicthin.ly/@handleview: follow, links, trust, conversion),Profile-edit.html(owner editor with live preview), andprofile-kit.jsx(shared icons + the singlePublicProfilerenderer used by both the public page and the editor preview — edit it once, both surfaces stay in sync).
Assets (assets/) — logo-wordmark.svg, brand illustration-*.svg,
marketing icon-*.svg.
Skill — SKILL.md (Agent-Skills compatible; README.md is the guide).
- Fonts are loaded from Google Fonts (the product does the same) — no local binaries needed. Bricolage Grotesque, Geist, Geist Mono, Rubik.
- Icons: Lucide is linked from CDN (matches the codebase's
react-icons/lu). - The brand carries two palettes: the modern app (violet + highlighter pastels,
documented above and used by this system) and a legacy marketing palette
(dark purple
#3f2b5b+ teal). New work should use the modern app palette; the teal survives only in brand illustrations.