Skip to content

Styling & Theming

Shaker uses the RetroUI design system, a NeoBrutalism-inspired design language featuring bold colors, thick borders, and solid shadows. The styling is implemented with NativeWind (Tailwind CSS for React Native) and centralized design tokens.

NeoBrutalism Aesthetic:

  • Bold, high-contrast colors
  • Thick black borders (3-6px)
  • Solid shadows (no blur)
  • Playful, energetic feel
  • Accessibility-first approach

Key Characteristics:

  1. Borders: 2-6px solid black borders on interactive elements
  2. Shadows: Offset shadows without blur for depth
  3. Colors: Vibrant, saturated colors with high contrast
  4. Typography: Bold weights for headings, clear hierarchy
  5. Spacing: Generous padding and margins
  6. Animations: Snappy, playful transitions (200-300ms)
colors: {
primary: '#FFD803', // Bright Yellow
secondary: '#FF6B9D', // Pink
accent: '#00D9FF', // Cyan
teal: '#14B8A6', // Teal (main brand color)
}
colors: {
success: '#10B981', // Green
warning: '#F59E0B', // Orange
error: '#EF4444', // Red
info: '#3B82F6', // Blue
}
colors: {
black: '#000000',
white: '#FFFFFF',
gray: {
50: '#F9FAFB',
100: '#F3F4F6',
200: '#E5E7EB',
300: '#D1D5DB',
400: '#9CA3AF',
500: '#6B7280',
600: '#4B5563',
700: '#374151',
800: '#1F2937',
900: '#111827',
}
}
fontSize: {
xs: 12, // Small labels
sm: 14, // Body text (small)
base: 16, // Body text (default)
lg: 18, // Large body text
xl: 20, // Small headings
'2xl': 24, // Medium headings
'3xl': 30, // Large headings
'4xl': 36, // Extra large headings
'5xl': 48, // Display text
}
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
}

Usage Guidelines:

  • Headings: bold (700) or extrabold (800)
  • Body text: normal (400) or medium (500)
  • Emphasis: semibold (600)
  • Buttons: bold (700)

Based on 4px base unit:

spacing: {
xs: 4, // 4px
sm: 8, // 8px
md: 16, // 16px (default)
lg: 24, // 24px
xl: 32, // 32px
'2xl': 48, // 48px
'3xl': 64, // 64px
'4xl': 96, // 96px
}
borders: {
thin: 2, // 2px - subtle borders
medium: 3, // 3px - standard borders
thick: 6, // 6px - prominent borders
}

Usage:

  • Cards: 3px border
  • Buttons: 3px border
  • Headers/Tab bar: 6px border
  • Inputs: 2px border

Solid offset shadows (no blur) for NeoBrutalism:

shadows: {
xs: { offsetX: 1, offsetY: 1 },
sm: { offsetX: 2, offsetY: 2 },
md: { offsetX: 4, offsetY: 4 }, // Default
lg: { offsetX: 6, offsetY: 6 },
xl: { offsetX: 8, offsetY: 8 },
'2xl': { offsetX: 12, offsetY: 12 },
}

Implementation:

shadowColor: '#000000',
shadowOffset: { width: 4, height: 4 },
shadowOpacity: 1,
shadowRadius: 0, // No blur
elevation: 0, // Android: no elevation
borderRadius: {
none: 0,
sm: 4,
md: 8, // Default
lg: 12,
xl: 16,
'2xl': 24,
full: 9999, // Circular
}

Centralized design tokens for runtime use:

import { designTokens } from '@/styles/designTokens';
// Colors
designTokens.colors.primary
designTokens.colors.teal.DEFAULT
designTokens.colors.gray[500]
// Typography
designTokens.typography.fontSize.xl
designTokens.typography.fontWeight.bold
// Spacing
designTokens.spacing.md
designTokens.spacing.xl
// Borders
designTokens.borders.thick
// Shadows
designTokens.shadows.md

Theme configuration for NativeWind:

tailwind.config.js
module.exports = {
theme: {
extend: {
colors: require('./styles/theme/colors'),
spacing: require('./styles/theme/extend/spacing'),
fontSize: require('./styles/theme/extend/typography').fontSize,
// ...
}
}
}
import { View, Text } from 'react-native';
<View className="bg-teal p-4 rounded-lg border-3 border-black shadow-retro-md">
<Text className="text-xl font-bold text-black">
Hello RetroUI
</Text>
</View>
<View className="p-4 sm:p-6 md:p-8">
<Text className="text-base md:text-lg lg:text-xl">
Responsive text
</Text>
</View>
<View className={`p-4 ${isActive ? 'bg-primary' : 'bg-gray-200'}`}>
<Text>Conditional styling</Text>
</View>
<View className="bg-white dark:bg-gray-900">
<Text className="text-black dark:text-white">
Adapts to color scheme
</Text>
</View>
<View className="bg-white border-3 border-black rounded-lg shadow-retro-md p-4">
<Text className="text-xl font-bold mb-2">Card Title</Text>
<Text className="text-base">Card content</Text>
</View>

Style Breakdown:

  • bg-white: White background
  • border-3: 3px border
  • border-black: Black border color
  • rounded-lg: 12px border radius
  • shadow-retro-md: 4px offset shadow
  • p-4: 16px padding
<TouchableOpacity className="bg-primary border-3 border-black rounded-lg px-6 py-3 shadow-retro-sm active:shadow-none active:translate-x-1 active:translate-y-1">
<Text className="text-base font-bold text-black text-center">
Press Me
</Text>
</TouchableOpacity>

Press Effect:

  • active:shadow-none: Remove shadow on press
  • active:translate-x-1 active:translate-y-1: Move button to simulate depth
<TextInput
className="bg-white border-2 border-black rounded-md px-4 py-3 text-base focus:border-primary"
placeholder="Enter text"
placeholderTextColor="#9CA3AF"
/>

Helper functions for creating RetroUI styles:

import { getRetroShadow, createRetroCard } from '@/styles/utils/retroUtils';
// Get shadow style
const shadowStyle = getRetroShadow('md');
// Returns: { shadowColor: '#000', shadowOffset: { width: 4, height: 4 }, ... }
// Create card style
const cardStyle = createRetroCard('#FFFFFF', 'md', 'lg');
// Returns complete card style object

Available Utilities:

  • getRetroShadow(size) - Get shadow style
  • getColoredShadow(color, size) - Get colored shadow
  • createRetroCard(bg, shadow, radius) - Create card style
  • createRetroButton(bg, shadow) - Create button style
  • getSpacing(size) - Get spacing value
  • getBorderRadius(size) - Get border radius value
  • combineStyles(...styles) - Merge style objects
  • getPressStyle() - Get press animation style

Pre-defined class name utilities:

import { retroCN } from '@/styles/baseStyles';
<View className={retroCN.card}>
<Text className={retroCN.heading}>Title</Text>
</View>

Available Classes:

  • retroCN.card - Card container
  • retroCN.cardPrimary - Primary colored card
  • retroCN.button - Button base
  • retroCN.input - Input base
  • retroCN.heading - Heading text
  • retroCN.body - Body text
animations: {
'retro-bounce': {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
'retro-press': {
'0%': { transform: 'translate(0, 0)' },
'100%': { transform: 'translate(4px, 4px)' },
},
'retro-shake': {
'0%, 100%': { transform: 'translateX(0)' },
'25%': { transform: 'translateX(-10px)' },
'75%': { transform: 'translateX(10px)' },
},
}
import { Animated } from 'react-native';
const bounceAnim = useRef(new Animated.Value(0)).current;
Animated.spring(bounceAnim, {
toValue: 1,
friction: 3,
tension: 40,
useNativeDriver: true,
}).start();
<Animated.View style={{ transform: [{ translateY: bounceAnim }] }}>
<Text>Bouncing!</Text>
</Animated.View>

All color combinations meet WCAG AA standards:

  • Primary on Black: 4.5:1 contrast ratio
  • White on Teal: 4.8:1 contrast ratio
  • Black on White: 21:1 contrast ratio

Minimum touch target size: 44pt × 44pt (iOS HIG standard)

<TouchableOpacity
className="min-h-[44px] min-w-[44px] items-center justify-center"
accessible={true}
accessibilityLabel="Add Recipe"
accessibilityRole="button"
>
<Icon name="plus" size="iconLg" />
</TouchableOpacity>
<View
accessible={true}
accessibilityLabel="Recipe card"
accessibilityHint="Double tap to view recipe details"
>
<Text>Recipe Name</Text>
</View>
import { Platform } from 'react-native';
<View style={{
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 4, height: 4 },
shadowOpacity: 1,
shadowRadius: 0,
},
android: {
elevation: 0, // No elevation for flat design
},
}),
}} />
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function MyScreen() {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}>
<Text>Content</Text>
</View>
);
}

Good:

backgroundColor: designTokens.colors.primary

Bad:

backgroundColor: '#FFD803'

Good:

<View className="bg-primary p-4 rounded-lg" />

Bad:

<View style={{ backgroundColor: '#FFD803', padding: 16, borderRadius: 12 }} />

Good:

className="bg-success text-white"

Bad:

className="bg-green-500 text-white"

Use the spacing scale consistently:

  • Small gaps: gap-2 (8px)
  • Medium gaps: gap-4 (16px)
  • Large gaps: gap-6 (24px)
  • Cards: shadow-retro-md
  • Buttons: shadow-retro-sm
  • Modals: shadow-retro-lg

To add custom colors or tokens, edit styles/theme/colors.js:

module.exports = {
// Existing colors...
custom: {
DEFAULT: '#YOUR_COLOR',
hover: '#HOVER_COLOR',
foreground: '#TEXT_COLOR',
},
};

Use the utility functions to maintain consistency:

import { createRetroCard, getRetroShadow } from '@/styles/utils/retroUtils';
const customCardStyle = createRetroCard(
designTokens.colors.accent, // Background
'lg', // Shadow size
'xl' // Border radius
);
<View style={customCardStyle}>
<Text>Custom Card</Text>
</View>

2026-01-05