DEV Community

Chris F A
Chris F A

Posted on Originally published at applighter.hashnode.dev

The Design Workflow That Makes an Expo Template Stop Looking Like an Expo Template

TL;DR

  • Six defaults give away almost every Expo template: blue-500, Inter, Lucide outline icons, borderRadius: 12 cards, borderRadius: 8 buttons, and a "Nothing here yet" empty state.
  • Three changes do most of the work in 45 minutes: one palette, one type pairing, one radius token.
  • Mixed radii (8 on buttons, 12 on cards) is the single most visible tell. Pick one.
  • Add exactly one motion primitive: press-in scale to 0.97, one shared spring config, everywhere.
  • Name your Figma variables the same as your code tokens. Export to JSON, feed it to your theme, stop doing per-screen updates.
  • The full polish plan is 12 to 14 focused hours across a week.

Every Expo template on the market ships with the same tell-tale visual signature: teal or blue primary color, Inter font, generic Lucide icons, and a card-with-shadow list style. Users can spot a template-fork from three seconds away.

The reason: template authors are engineers, and engineers ship default themes. Founders inherit those defaults, launch, and their app blends into every other Expo template on the App Store.

Here's the design workflow I use to turn a template into something that looks like a real product. The core of it, the part that does most of the work, is 45 to 60 minutes.

The default look: what to notice

Almost every Expo template ships with:

  1. Primary color: teal-500, blue-500, or purple-500 from Tailwind.
  2. Body font: Inter, sometimes Manrope.
  3. Icons: Lucide, Heroicons, or Ionicons in outline weight.
  4. Card style: borderRadius: 12, shadow-md, white background.
  5. Button style: borderRadius: 8, primary color fill.
  6. Empty state: a small centered illustration plus "Nothing here yet."

The combination is instantly recognizable as "generic template." Users are trained to distrust it.

Three visual changes that de-generic-ify (45 min total)

1. Palette (15 min).

Pick a primary color that's NOT teal or blue-500. Coolors.co or Radix Colors' scales are good sources. Set:

  • primary: your brand color, a saturated hue distinct from defaults
  • bg: off-white or off-black, never pure #FFFFFF (pure white looks synthetic)
  • surface: a slight tint off bg
  • text: a warm dark like #171717, not pure black

The whole thing wants to live in one file:

// theme.ts
export const theme = {
  colors: {
    primary: '#5B3DF5',
    bg: '#FAFAF9',
    surface: '#F4F4F2',
    text: '#171717',
  },
  radius: { md: 12 },
  space: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32 },
} as const;
Enter fullscreen mode Exit fullscreen mode

Wire it once, then search-replace the template's default hex values. If your template scatters raw hex across screens instead of centralizing it, that 15 minutes becomes an afternoon. Applighter templates keep palette, type, and radius as configurable variables for exactly this reason.

2. Typography (15 min).

Swap Inter for something with more character. Two safe non-Inter picks in 2026:

  • Geist (Vercel): modern, still neutral, but not Inter.
  • Manrope: friendlier, slightly rounder.

Or commit fully:

  • Space Grotesk for headings plus Inter for body. The mismatch reads as intentional.
  • DM Serif Display for headings plus DM Sans for body. Signals design-forward.

Load with expo-font. Never load more than 2 font families.

3. Corner radius (15 min).

Search your codebase for borderRadius: 8 and borderRadius: 12. Pick ONE radius token and use it everywhere. Common quality signals:

  • 4: sharp, technical (Linear, Vercel)
  • 12: friendly, standard (Apple's default)
  • 20: soft, consumer (Instagram, Airbnb)

Mix-and-match radii is the single most visible "template" tell. If you do nothing else in this post, do this one.

Motion primitives that signal quality

Add ONE motion primitive to every interactive element:

  • Buttons: scale to 0.97 on press-in, back to 1.0 on release. 100ms.
  • Cards: subtle scale on press if tappable.
  • Screen transitions: use Expo Router's default fade, not the aggressive iOS slide.

One library: react-native-reanimated, already in most templates. One shared spring config, one wrapper component, used everywhere:

import { Pressable } from 'react-native';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';

const spring = { damping: 20, stiffness: 300 };

export function PressScale({ children, onPress }) {
  const scale = useSharedValue(1);
  const style = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  return (
    <Pressable
      onPressIn={() => (scale.value = withSpring(0.97, spring))}
      onPressOut={() => (scale.value = withSpring(1, spring))}
      onPress={onPress}
    >
      <Animated.View style={style}>{children}</Animated.View>
    </Pressable>
  );
}
Enter fullscreen mode Exit fullscreen mode

Wrap your buttons and tappable cards in that and you're done. Applied consistently, this alone changes how the app feels, from "template" to "considered."

Icons and illustrations

Lucide icons are fine but instantly recognizable. Two paths off the default:

  1. Phosphor Icons (free, huge set, 6 weights). Swap the import: import { Heart } from 'phosphor-react-native'.
  2. Custom SVGs for the 5 most-visible icons. Your tab-bar icons, your empty state, your loading state. Commission or draw these. react-native-svg renders them at any size.

For empty states: replace the generic illustration with something specific to your product. "No orders yet" beats "No items yet" beats a stock cart drawing.

The one Figma-to-code handoff that saves the most time

If you're designing in Figma alongside the app, do THIS one thing:

Define your color, spacing, and radius tokens in Figma with the SAME names as in your code. primary, bg, surface, space-2, space-4, radius-sm, radius-md.

Then use Figma's Variables export to a JSON file. Feed that JSON into your Expo theme. Every design change flows through the token file, with no per-screen updates.

The ship-in-a-week visual polish plan

Day 1 (2 hrs): audit the template's default palette, type, radius, icons. Note every hex, every borderRadius, every icon library.

Day 2 (3 hrs): pick and apply the palette, type, and radius. Ship a build. Look at it on your device.

Day 3 (2 hrs): swap the icon set. Replace the 5 most-visible custom illustrations.

Day 4 (2 hrs): add motion primitives. Buttons, cards, screen transitions.

Day 5 (2 hrs): design 3 empty states specific to your product.

Day 6-7: user-test with 3 people. Watch what they say when they first open the app. Fix the visual bits they call out.

Total: roughly 12 to 14 focused hours. Your app stops looking like a template.

Why this matters

Users make a trust decision fast, and most of it happens before they read a word of your copy. If your app looks like every other template, they've seen a lot of similar apps this year and their trust default is "probably not that good."

A distinct visual identity, even a rough one, signals someone actually cared. That's worth a lot more than the fastest possible time-to-ship.

Which of the six default tells is still in your app right now? I'm betting on the mixed radii. Drop yours in the comments.

Top comments (1)

Collapse
 
morphoices profile image
MORPHOICΞS.

I believe the greatest benefit is limiting your decisions, rather than trying to “make the template look good.” ~

Palette, type, radius, motion, icons, that’s a much easier checklist to reason about.

Many generic-looking interfaces do not lack creativity but are missing a few conscious decisions that make the product feel like it’s owned by someone.