• Swivl
  • Product Designer
  • 2025 to 2026

Cut design time by 50% and made a six-week product revamp possible.

Swivl is a field service platform. Office teams run jobs, estimates, invoices and schedules on desktop, and technicians use it on their phones. In June 2025, three of us started building the design system behind every one of those screens: the parts in Figma, the rules in Notion, and the same scales the code uses. This is the story of what we decided, and what it changed.

Metric

50%

Less time to design a new screen

Was about 30 min, now about 15 min

1–2

Design QA issues per feature

Down from double digits

70%

New screens built only from library parts

Up from about 40%

95%

Colours on core components come from tokens

Counted on 12 core component pages

Voice noteAbhi walks you through it
0:00 / 1:30
V28Hero · Popover redlinesNewBuilt in code7.5 s · loops
ShowThe real Popover at its real spec, 512 wide. Red size lines, the magenta 2px title gap, blue spacing guides with token chips, then radius, border and shadow, each appearing in turn like Figma's inspect, then fading before the loop.SourcePopover component set (read only); every value checked against the Theme file.

The 30-second version

The challenge

Swivl had grown fast, and nothing was written down. Designers copied hex codes from old frames, and the same job got a different answer depending on who drew it. Then the ask landed: revamp the whole product and ship a run of big new features, on a deadline counted in weeks, not months.

What we did

We built the system first, so every part made once would serve the revamp and every feature after it. Colours sit in three layers, so a rebrand or dark mode is one change, and one rule with five named sizes replaced about ten pop-up widths. The revamp took six weeks.

The key insight

We didn't need more components. We needed decisions: which button, which pop-up, which colour, and why. A design system is a decision system, not a component library.

My role

  • Product Designer 2, one of three designers on the system, June 2025 to June 2026
  • Foundations: colour, type, spacing, icons and illustration
  • Component docs, the pop-up rule and its code audit
  • The audit after v1: all 79 library pages checked against the docs

The team

  • Victoria, Lead Product Designer
  • Aashish, Product Designer
  • The Swivl engineering team

The constraints

  • 3 designers, 15+ modules

    Feature work couldn't stop while we built the system.

  • Tailwind and shadcn/ui

    Engineers already built with both.

  • Desktop and phone

    Every screen had to work on a phone.

The whole system at a glance

One map, the whole story. The two numbered boxes are the decisions everything else hangs on. Decision 1 puts every colour in three tiers, so a rebrand or dark mode is one change instead of a redraw. Decision 2 swaps about ten pop-up widths for one rule and five named sizes. If you read only two things on this page, read those.

V00Bird's-eye mapNewBuilt in codeLive text · scales to the column
ShowThe map first, then the page walks through it in the same order.NoteThe 50% in "The result" is the same figure as in the hero and in What changed.

The principles behind every decision

With three designers and no time to argue, we needed a way to say no quickly. So before we drew a single component, we agreed on four principles. Every decision on this page traces back to them, and they are the reason two of us, working alone, reach the same answer.

V27Three principlesMustCoded · 3 rows · 15 s loop
ShowScalable: a menu stops at 8 rows, then scrolls and gets a search. Familiar and reusable: instead of a new IconButton, icon only becomes a switch on the Button. AI-assisted: AI suggests two changes, and the designer accepts one and dismisses the other.

What we built on

We didn't build what we could borrow. Tailwind gave us spacing, radius and breakpoints, shadcn/ui gave us the components and their names, Lucide the icons, Geist the type. Borrowing meant design and code spoke one language from day one.

  • Tailwind CSSSpacing, radius and breakpoints
  • shadcn/uiComponents and their names
  • LucideIcons
  • GeistType
  • Untitled UI ProMarketing site variables

FoundationsKey decision 1

Colours live in three layers, and screens use only the top one

Colour came first, because it was about to bite us twice. The brand had already moved once, from magenta to cyan, and dark mode was coming. We wanted one change to reach every screen. So colours sit in three tiers: Mode (light or dark), Theme (the brand) and TailwindCSS (the raw palette). Designers and screens only ever touch Mode, where each colour is named by its job, primary, card, warning, not by its shade: 73 colours, each with a light and a dark value.

WhyThe brand had already moved once, and dark mode was next. One change had to reach every screen.

CostMore setup up front, and designers had to learn which tier to pick.

What it changedA brand or dark-mode change is now a variable switch, not a redraw. 97.7% of colour bindings sit on the top tier.

Built on the shadcn/ui Kit for Figma, which mirrors how shadcn/ui themes its code. The tier names and the rule come from our Color doc in Notion.

Foundations

Design and code use the same scales

Colour was the hard tier. The other foundations were easier, because of one rule: design uses the code's scales, not its own. Spacing, radius and breakpoints follow Tailwind, so spacing 4 in Figma is p-4 in code and nothing needs translating at handoff. The tokens cover the product only; marketing pages use Untitled UI's Pro Variables instead, which keeps the product set small and stable.

V07Figma next to codeNiceImage, split in two
VariablesFigma · Swivl Theme
spacing/416
radius/rounded-md6
breakpoint/md768
// the same names in code
<div className="p-4 rounded-md md:flex">
  …
</div>
ShowThe Figma variables panel on the left, the same names in code on the right.SourceTheme variables and a code snippet

Foundations

Type, spacing and icons are picked by role

Then everything got a name by its job, not its size. Type is Geist, with 12 roles from page title to table cell, built on 41 text styles that are all bound to variables. Spacing steps by 4, 35 steps from 0 to 384, so if a design wants 18px, the answer is 16 or 20. Six named radii sit on top of Tailwind's ten, so a designer chooses from six, not ten.

V08Type and spacingNiceImage or a 6 s loop
Text roles, 6 of 12
Job detailsh1 · 36/40
Job detailsh2 · 30/36
Job detailsh3 · 24/32
Job detailsh4 · 20/28
Job detailsp · 16/28
Job detailssmall · 14/20
Spacing steps up by 4. Half-steps (2, 6, 10 and 14) are for icons only.
4
8
12
16
24
32
48
64
ShowThe 12 text roles stacked with size and line height, spacing bars stepping up by 4, and a card's corners easing through the six radii.SourceTheme variables

Icons come from Lucide, in six sizes from 16 to 64px, with 10 custom icons kept for real AI features.

Components

Component anatomy

With the foundations in place, components could be built to be set up, never detached. In the Button, every part is a switch, a swap or a token: one component covers 3 types, 4 states and 3 sizes, 36 variants, and icon-only is just Show Text turned off. The Popover works the same way. Its middle is a native Figma slot, so one component holds any content, and nobody has to break it apart to use it.

V22Popover anatomyNewBuilt in codeOptional: swap for a Figma screenshot
Anatomy of the Swivl Popover The Popover, 512 pixels wide in Figma, drawn smaller. It is a vertical auto layout frame with padding bound to spacing/6 and gaps bound to spacing/4. Inside: a header with the Icon forward instance and the Title and Description text properties, a native Figma slot for any content, and a footer button group. {Title} {Description} Slot (swap it with your content) Cancel Link Button Button Button Icon forward 20 variants shape × size × type Title · Description text properties each one can hide Popover 1 slot max-w-lg · 512 Slot native Figma slot any content fits Button group footer 5 switches Auto layout padding spacing/6 gap spacing/4
Popover

NEW Swivl Component Lib

SlotYour content
_Popover headernested
Title{Title}
Description{Description}
Is Title?
Is description?
Is Forward Icon?
ShowThe Popover in Figma with its parts marked. Swap the slot's content, then hide the description.SourcePopover component (read only; nothing in Figma was changed)

Components

Components in motion

This is what that looks like in use. Eight parts from the library doing everyday jobs: saving an invoice, typing an amount, sending a reminder, marking an invoice paid. They all run on the same tokens, so one switch turns the whole grid from light to dark.

V25Components in motionNewBuilt in codeLive loops · 8–16 s · Light and Dark
Real Swivl components · NEW Swivl Component Lib
ShowEight real components on a loop. The toggle switches between the Light and Dark mode tokens.SourceComponent Lib (read only; nothing was changed)

PatternsKey decision 2

One rule for which pop-up to use

Parts were only half the problem. We also had nine kinds of overlay and no rule for choosing between them, so the same task opened in different ways depending on who designed it. I wrote one rule, pick the pop-up by the task, then checked it against the code.

WhySimilar tasks opened in different ways, and the code had 394 one-off widths.

Cost14 record forms have to move from centred pop-ups to side panels.

What it changedCreating or editing a record opens a side panel, deleting asks in a small dialog, and multi-step flows get a large dialog. Five named sizes replace about ten widths.

Docs and governance

Rules people can find and trust

A system nobody trusts gets copied around instead of used. So every part got a page. Figma shows how it looks, Notion explains when and why, and each Notion page links to its Figma frame.

One page format

What it's for, its parts, variants and states, light and dark, and do's and don'ts.

No silent updates

Every change gets a changelog entry: the date, what changed, why, and who.

Known quirks, written down

For example, a colour token named "brand-blue" that is actually purple. Written-down quirks are harmless; hidden ones break trust.

A ticket per page

Most doc pages link their own Linear ticket, so the work is visible and easy to track.

V16Notion walkthroughMustScreen recording · 15–20 s
Design SystemFoundationsComponentsButtonsPatternsChangelog
Buttons
Every change needs a changelog entry. No silent updates.
DateChangeWhyBy
————
ShowThe Notion hub, then Foundations, a component page and its changelog, then known quirks. Zoom in on "no silent updates".SourceDesign System hub. Fix the Notion copy slips before you record: Tabs repeats the Breadcrumbs rules, Top Navigation links the Badge ticket, Icon Forward has its circle and square corners swapped, and the hub still says "coming soon".

Docs and governance

Moving everyone over gently

Nobody was forced over on day one. Old components stay in the library marked Deprecated, with a short swap guide, and move to a separate file once nobody uses them.

V17Component swapNiceScreen recording · 8–10 s
ShowIn Figma, select a deprecated button, swap it for the new one, set the variant, done.SourceComponent Lib

Design ↔ Dev

One set of names from Figma to Storybook

The names travel all the way into code. There is no export file in the middle: Figma variables use the same Tailwind and shadcn/ui names as the code, so base/border/border in Figma is --border in globals.css and border-border in a component. Every push to main rebuilds the Storybook, 93 components today, and every pull request is checked for raw hex.

V24Design ↔ Dev pipelineNewBuilt in codeFrom real tickets, not a mock
SourceLinear FE-2138 (Tailwind v4 and shadcn/ui), FE-2309 (primitives), FE-2323 (Storybook deploy), BLU-1461 (mobile colours)

What changed

Then the revamp came, and it took six weeks. Adding a big new feature stopped being a project, handoff got cleaner, and the library became the default way to build.

V19Number cardsMustBuilt in codeCount up once when real numbers are in

50%

less time to design a new screen

about 30 min → about 15 min

Typical for a new screen built from library parts. A full flow now takes a day or two.

1–2

design QA issues per big feature

double digits → 1–2

Counted on each big feature shipped after June 2025. Before the system, a feature came back with ten or more.

70%

of new screens built only from library parts

about 40% → 70%

Share of new screens with nothing detached or drawn one-off, before and after the system started in June 2025.

NumbersAbhi's before-and-after, 3 Oct: about 30 → 15 min per screen, double digits → 1–2 QA issues per big feature, about 40% → 70% of new screens built only from library parts.Addone real line from an engineer or PM under the cards.

The audit after v1

A system drifts the moment you stop looking. Three months after launch, I checked all 79 Figma pages against the docs and the code, to catch the drift before it spread. Here is what it found, and the three decisions it forced.

What it exposed

V20Audit scorecardNewBuilt in codeAnimate later: each gap ticks toward its target

Components with a Notion page

14 of 73

Component sets with a Figma description

3 of 266

Component sets with a focus state

12 of 266

Variables with a code name for developers

0 of 759

Places where two docs disagree

10
ShowThe five gap numbers as large labels, then each one ticking toward its target.SourceAudit and Decisions

Three decisions it led to

Rolling out now
1

Red for delete buttons

The code already used red, so the docs now do too: a Destructive style, with the verb label and confirm step kept.

2

One modal size scale

Three docs had three scales. We kept the engineering one: 400, 512, 672, 896px and full screen.

3

One status colour map

"Closed" was grey in Tasks and blue in Leads. Now each status word has one colour.

Screens

How the final screens look after the design system

This is what it all adds up to. Five live Swivl screens, built from the same parts. The sidebar, the page header, the cards, the tables and the buttons come straight from the library, so a new screen looks like it belongs on day one.

V26Final screensNew5 screens · 1.6 s each · loops
AI Receptionist: Call logs Website builder: Traffic overview Customer details: Profile and work order history Smart template: AC repair and safety checklist Task: Clocked out, ready to close
AI Receptionist · Call logs
ShowAI Receptionist · Website builder · Customer details · Smart template · Task: each screen holds for 1.6 s, then the next one fades in. Hover to pause; the small bars jump to a screen.SourceFigma, exported read-only at 2× and cut to 1440×1080 from the top. Names and numbers on the screens are Figma sample data.