Design systems for small app teams: start smaller than you think
How a small app team can build a design system that lasts: tokens first, a dozen components, notes in the design file, clear names and a first month plan.

Ask a small app team to search its codebase for hard-coded colors and you get a familiar silence. The brand blue turns up in four slightly different hex values, there are more grays than anyone can explain, and two screens use a red that exists nowhere in the design file. Nobody did anything wrong. Each value was a reasonable choice on the day someone made it.
That drift is what a design system fixes, and a small team doesn't need the version they've seen from large companies, with a public website, hundreds of pages and a team of its own. It needs a shared vocabulary small enough to stay true: a set of tokens, about a dozen components, notes where people already look, a naming rule and a way to change things without breaking them. Everything else can wait until there's a reason for it.
Start with tokens, not components
Tokens are named design decisions: a color, a type style, a spacing step. Components are built from them, so getting the tokens right first means every later component inherits consistent values for free.
Color in two layers
Keep two layers of color. The first is a primitive palette, the raw values with neutral names such as blue-500 or
gray-100. The second is a set of semantic roles that describe a job: text.primary, text.secondary,
surface.raised, action.primary, status.danger. Screens and components use only the semantic layer. Dark mode
then becomes a second set of values for the same semantic names rather than a parallel design.
Both platforms already think this way. iOS has semantic system colors such as label and systemBackground that
adapt to appearance and accessibility settings. Material 3 describes its
design tokens in tiers, from reference tokens holding
raw values to system tokens such as md.sys.color.primary that assign roles, and component tokens on top. Borrow
the structure even if you don't use Material.
The semantic layer is also the right place to check contrast. If text.secondary on surface.raised passes in
both light and dark mode, every screen that uses that pair passes too. The thresholds are the ones in my
mobile accessibility checklist: 4.5:1 for body text, 3:1 for large
text and the parts of controls people need to see.
Type by role
Define six to eight text styles named by what they do (title, headline, body, callout, caption) rather
than by size. A name like text-17 stops being true the moment someone changes the size. Map each style to the
platform's scaling: on iOS, relate custom fonts to a system text style so Dynamic Type still works; on Android, set
sizes in sp. A type scale that ignores the user's text size setting is a bug, not a style choice.
Spacing, radius and elevation
Pick a base unit of 4 or 8 points and a short scale on top of it. Five to seven steps is plenty. The same goes for corner radius (two or three values) and elevation or shadow (two or three levels). Fewer values make each choice quicker and make inconsistencies visible in review.
enum Space {
static let xs: CGFloat = 4
static let s: CGFloat = 8
static let m: CGFloat = 16
static let l: CGFloat = 24
static let xl: CGFloat = 32
}
A dozen components, not fifty
A component earns a place in the system the third time it's needed, not the first. Until then it lives in the feature that uses it. This one rule keeps a small library from filling up with one-off banners that nobody reuses.
Where the platform already provides a component, wrap it rather than redraw it. When Apple shipped iOS 26 with its Liquid Glass design in September 2025, apps rebuilt with the new SDK picked up the new material in standard bars and controls, while custom-drawn versions kept whatever someone had hard-coded. A thin wrapper over the system tab bar costs little and follows the platform when it changes.
| Component | Start with | Leave for later |
|---|---|---|
| Button | Primary, secondary, text; normal and disabled | Icon plus label, loading state |
| Icon button | One size, with an accessible label built in | Toggle variant |
| Text field | Label, helper text, error | Prefix and suffix, character counter |
| List row | Title, subtitle, trailing chevron or switch | Swipe actions |
| Card | One padding, one radius, one elevation | Media variants |
| Top bar | System bar wrapped, title and actions | Large and collapsing variants |
| Tab bar | System component wrapped | Badges |
| Sheet | System sheet with standard handle and actions | Custom heights |
| Dialog | Title, message, two actions | Input dialogs |
| Snackbar or toast | Message plus one action | Queueing rules |
| Empty state | Line of text, one action, optional image | Illustrated variants |
| Loading | Spinner and a skeleton for list rows | Shimmer effects |
The empty state is on that list on purpose. It's the screen new users see first, and I made the case for treating it as teaching space in onboarding flows people actually finish.
Documentation where people already look
Small teams don't read documentation sites; they read the design file and the code. Put usage notes next to each component in the design file, a few lines on when to use it and when not to, and keep a short page of decisions, such as "only one primary button per screen" or "destructive actions always ask for confirmation in a dialog". Most design tools, Figma included, let you attach a description to a component that people see when they pick it from the library.
In code, the best documentation is a preview that shows every variant: a SwiftUI #Preview or a Compose @Preview
per state. Those previews are also natural targets for snapshot tests, which catch the change that quietly moves
every button two points to the left; Tomasz covered the setup in his
snapshot testing field guide.
Naming that survives
Names outlive values, so spend more time on them than on the colors. A few rules I hold to:
- Name by role, not appearance:
status.danger, neverred. The brand might turn orange. - Use the same names in the design file and in code. If the designer says "surface raised" and the developer
searches for
surfaceRaised, they should land on the same thing. - Write down one separator convention per context, such as dots in the design file and camelCase in Swift and Kotlin, and never mix them.
- Never name a component after the screen it first appeared on.
CheckoutButtonwill end up on the profile screen.
If you export tokens from a design tool into code, look at the format the Design Tokens Community Group published
as its first stable version, 2025.10, on 28 October 2025. It's a W3C Community Group report rather than a formal
W3C standard, but it gives you a vendor-neutral JSON structure, with $value and $type properties and aliases
written in curly braces, which more and more tools can read. The
format specification is long; the
sections on groups and aliases are the ones a small team needs first.
Versioning without ceremony
Use semantic versioning in plain terms. A patch release fixes a value. A minor release adds a token or a component. A major release renames or removes something, which means work for everyone using it. Keep a changelog with a few lines per release, and when you rename a token, keep the old name as an alias for one release so screens can move over gradually.
Version the tokens and the code components together, tag the release in the repository, and publish the design library with a note that matches the changelog. If the version in the design file and the version in the app ever disagree, believe the app and fix the file.
When to formalize
For a team of three to ten, a token file, a library page in the design file and a dozen components are enough for a long time. The signs that you need more structure are specific: a second app or a second platform team starts using the same components, someone joins and takes weeks to learn which pieces are current, a rebrand is coming, or the same question about which gray to use comes up for the third time in a month. Even then, add an owner and a simple way to propose changes before you add a documentation site.
The first month
- Week one: screenshot every screen in the app and collect every color, font size, spacing value and corner radius in use. Count them. The count is your baseline.
- Week one: define the primitive palette, the semantic color roles, the type scale and the spacing scale. Check contrast for every text and surface pair in both appearances.
- Week two: wire the tokens into code on each platform and convert one real screen to use only tokens. Fix whatever that screen reveals about the token set.
- Weeks two and three: build the first six components from the table above, in the design file and in code, with the same names and the same variants.
- Week three: write the usage notes, add previews for every variant, and set up snapshot tests for them.
- Week four: call it version 1.0.0, write the first changelog, and walk the team through it in half an hour. Agree on one place where changes are proposed and one person who decides.
- End of week four: choose the next six components based on what the next feature on the roadmap needs, not on what a complete library would contain.
More pieces on interface details and team practice live in the app design track. If you start anywhere, start with that search for hard-coded hex values. Write the number on a sticky note, and watch it go down as the first screens move to tokens.
Keep reading

App store screenshots that explain the app in five seconds
How to plan app store screenshots that work at search size: what the first three must show, how to write captions, and how to handle languages and dark mode.

Mobile app accessibility: a working checklist for small teams
A release by release accessibility checklist for small mobile teams: screen readers, text scaling, touch targets, contrast, icon labels and motion settings.

Onboarding flows people actually finish: what to cut and what to keep
Why mobile onboarding loses people before they start, four patterns that keep them, and a step by step audit of your own flow with what to measure.