Good Deeds Design System
Technical implementation guide for the Good Deeds brand and UI components.
Overview
This design system provides developers and designers with the tools, components, and guidelines needed to build consistent, accessible, and high-quality Good Deeds experiences across all platforms.
Design System Principles
- Token-Based - All design decisions are codified as design tokens
- Component-Driven - Build with reusable, composable components
- Accessibility-First - WCAG AA compliance is built-in, not bolted on
- Platform-Aware - Respects platform conventions while maintaining brand consistency
- Developer-Friendly - Clear documentation, typed interfaces, easy integration
Design Tokens
Design tokens are the atomic design decisions that make up our visual language. They should be implemented as constants/variables in your codebase.
Color Tokens
// colors.ts
export const colors = {
// Primary
primary: {
main: '#4CAF50',
light: '#80E27E',
dark: '#087F23',
contrast: '#FFFFFF',
},
// Secondary
secondary: {
main: '#2196F3',
light: '#6EC6FF',
dark: '#0069C0',
contrast: '#FFFFFF',
},
// Accent Colors
accent: {
warmth: '#FF9800',
sunshine: '#FFC107',
joy: '#9C27B0',
},
// Semantic
success: '#4CAF50',
warning: '#FF9800',
error: '#F44336',
info: '#2196F3',
// Neutrals
text: {
primary: '#212121', // 87% opacity equivalent
secondary: '#757575', // 60% opacity equivalent
disabled: '#9E9E9E', // 38% opacity equivalent
hint: '#BDBDBD', // 30% opacity equivalent
},
// Backgrounds
background: {
default: '#FFFFFF',
paper: '#FFFFFF',
surface: '#F5F5F5',
},
// Borders & Dividers
divider: '#E0E0E0',
border: '#E0E0E0',
// Overlays
overlay: {
light: 'rgba(255, 255, 255, 0.9)',
medium: 'rgba(0, 0, 0, 0.5)',
dark: 'rgba(0, 0, 0, 0.7)',
},
} as const;/* colors.css - CSS Custom Properties */
:root {
/* Primary */
--color-primary: #4CAF50;
--color-primary-light: #80E27E;
--color-primary-dark: #087F23;
--color-primary-contrast: #FFFFFF;
/* Secondary */
--color-secondary: #2196F3;
--color-secondary-light: #6EC6FF;
--color-secondary-dark: #0069C0;
--color-secondary-contrast: #FFFFFF;
/* Accent */
--color-warmth: #FF9800;
--color-sunshine: #FFC107;
--color-joy: #9C27B0;
/* Semantic */
--color-success: #4CAF50;
--color-warning: #FF9800;
--color-error: #F44336;
--color-info: #2196F3;
/* Text */
--color-text-primary: #212121;
--color-text-secondary: #757575;
--color-text-disabled: #9E9E9E;
--color-text-hint: #BDBDBD;
/* Backgrounds */
--color-background: #FFFFFF;
--color-surface: #F5F5F5;
/* Borders */
--color-divider: #E0E0E0;
--color-border: #E0E0E0;
}Typography Tokens
// typography.ts
export const typography = {
fontFamily: {
primary: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
display: '"Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
mono: '"JetBrains Mono", "Fira Code", "Courier New", monospace',
},
fontSize: {
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.5rem', // 24px
'3xl': '1.75rem', // 28px
'4xl': '2.25rem', // 36px
'5xl': '3rem', // 48px
},
fontWeight: {
regular: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
},
lineHeight: {
tight: 1.1,
snug: 1.2,
normal: 1.5,
relaxed: 1.6,
},
letterSpacing: {
tighter: '-0.02em',
tight: '-0.01em',
normal: '0',
wide: '0.01em',
wider: '0.02em',
},
} as const;
// Predefined text styles
export const textStyles = {
hero: {
fontFamily: typography.fontFamily.display,
fontSize: typography.fontSize['5xl'],
fontWeight: typography.fontWeight.extrabold,
lineHeight: typography.lineHeight.tight,
letterSpacing: typography.letterSpacing.tighter,
},
h1: {
fontFamily: typography.fontFamily.display,
fontSize: typography.fontSize['4xl'],
fontWeight: typography.fontWeight.bold,
lineHeight: typography.lineHeight.snug,
letterSpacing: typography.letterSpacing.tight,
},
h2: {
fontFamily: typography.fontFamily.display,
fontSize: typography.fontSize['3xl'],
fontWeight: typography.fontWeight.bold,
lineHeight: typography.lineHeight.snug,
letterSpacing: typography.letterSpacing.tight,
},
h3: {
fontFamily: typography.fontFamily.display,
fontSize: typography.fontSize['2xl'],
fontWeight: typography.fontWeight.semibold,
lineHeight: typography.lineHeight.snug,
},
h4: {
fontFamily: typography.fontFamily.primary,
fontSize: typography.fontSize.xl,
fontWeight: typography.fontWeight.semibold,
lineHeight: typography.lineHeight.normal,
},
bodyLarge: {
fontFamily: typography.fontFamily.primary,
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.regular,
lineHeight: typography.lineHeight.relaxed,
},
body: {
fontFamily: typography.fontFamily.primary,
fontSize: typography.fontSize.base,
fontWeight: typography.fontWeight.regular,
lineHeight: typography.lineHeight.relaxed,
},
bodySmall: {
fontFamily: typography.fontFamily.primary,
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.regular,
lineHeight: typography.lineHeight.normal,
},
caption: {
fontFamily: typography.fontFamily.primary,
fontSize: typography.fontSize.xs,
fontWeight: typography.fontWeight.medium,
lineHeight: typography.lineHeight.normal,
letterSpacing: typography.letterSpacing.wide,
},
button: {
fontFamily: typography.fontFamily.primary,
fontSize: typography.fontSize.base,
fontWeight: typography.fontWeight.semibold,
lineHeight: typography.lineHeight.normal,
letterSpacing: typography.letterSpacing.wider,
},
} as const;Spacing Tokens
// spacing.ts
export const spacing = {
0: '0',
1: '0.25rem', // 4px
2: '0.5rem', // 8px
3: '0.75rem', // 12px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
8: '2rem', // 32px
10: '2.5rem', // 40px
12: '3rem', // 48px
16: '4rem', // 64px
20: '5rem', // 80px
24: '6rem', // 96px
} as const;Border Radius Tokens
// borderRadius.ts
export const borderRadius = {
none: '0',
sm: '4px',
md: '8px',
lg: '16px',
xl: '24px',
full: '9999px',
} as const;Shadow Tokens
// shadows.ts
export const shadows = {
none: 'none',
sm: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
md: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',
lg: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
xl: '0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)',
} as const;Animation Tokens
// animation.ts
export const animation = {
duration: {
instant: '100ms',
fast: '200ms',
normal: '300ms',
slow: '500ms',
},
easing: {
linear: 'linear',
easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
easeOut: 'cubic-bezier(0, 0, 0.2, 1)',
easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
},
} as const;Breakpoint Tokens
// breakpoints.ts
export const breakpoints = {
mobile: '0px',
tablet: '640px',
desktop: '1024px',
wide: '1440px',
} as const;Component Library
Button Component
Variants
Primary Button
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
icon?: ReactNode;
iconPosition?: 'left' | 'right';
fullWidth?: boolean;
}Specifications:
/* Primary Button */
.button-primary {
background-color: var(--color-primary);
color: var(--color-primary-contrast);
border: none;
border-radius: 4px;
padding: 12px 24px;
font-family: var(--font-primary);
font-size: 1rem;
font-weight: 600;
line-height: 1.5;
letter-spacing: 0.02em;
min-height: 44px;
cursor: pointer;
transition: all 200ms ease-out;
}
.button-primary:hover {
background-color: var(--color-primary-dark);
box-shadow: var(--shadow-md);
}
.button-primary:active {
background-color: var(--color-primary-dark);
box-shadow: var(--shadow-sm);
transform: translateY(1px);
}
.button-primary:disabled {
background-color: var(--color-text-disabled);
cursor: not-allowed;
box-shadow: none;
}
/* Secondary Button */
.button-secondary {
background-color: transparent;
color: var(--color-primary);
border: 1px solid var(--color-primary);
border-radius: 4px;
padding: 12px 24px;
min-height: 44px;
}
.button-secondary:hover {
background-color: rgba(76, 175, 80, 0.08);
}
/* Tertiary Button */
.button-tertiary {
background-color: transparent;
color: var(--color-primary);
border: none;
padding: 12px 16px;
min-height: 44px;
}
.button-tertiary:hover {
background-color: rgba(76, 175, 80, 0.08);
}
/* Size Variants */
.button-small {
padding: 8px 16px;
font-size: 0.875rem;
min-height: 36px;
}
.button-large {
padding: 16px 32px;
font-size: 1.125rem;
min-height: 52px;
}Usage Examples
// React/React Native
<Button variant="primary" size="medium">
Help Now
</Button>
<Button variant="secondary" icon={<AddIcon />} iconPosition="left">
Create Request
</Button>
<Button variant="primary" loading>
Submitting...
</Button>Input Components
Text Input
interface TextInputProps {
label: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
helperText?: string;
error?: string;
disabled?: boolean;
required?: boolean;
type?: 'text' | 'email' | 'password' | 'tel' | 'number';
startIcon?: ReactNode;
endIcon?: ReactNode;
}Specifications:
.input-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
}
.input-label {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-primary);
margin-bottom: 4px;
}
.input-label-required::after {
content: ' *';
color: var(--color-error);
}
.input-field {
width: 100%;
min-height: 48px;
padding: 12px 16px;
font-size: 1rem;
font-family: var(--font-primary);
color: var(--color-text-primary);
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 4px;
transition: all 200ms ease-out;
}
.input-field:focus {
outline: none;
border: 2px solid var(--color-primary);
padding: 11px 15px; /* Adjust for thicker border */
}
.input-field.error {
border: 2px solid var(--color-error);
}
.input-field:disabled {
background-color: var(--color-surface);
color: var(--color-text-disabled);
cursor: not-allowed;
}
.input-helper-text {
font-size: 0.75rem;
color: var(--color-text-secondary);
margin-top: 4px;
}
.input-error-text {
font-size: 0.75rem;
color: var(--color-error);
margin-top: 4px;
}Card Component
interface CardProps {
elevation?: 'none' | 'sm' | 'md' | 'lg';
padding?: keyof typeof spacing;
radius?: keyof typeof borderRadius;
children: ReactNode;
onClick?: () => void;
hoverable?: boolean;
}Specifications:
.card {
background-color: var(--color-background);
border-radius: 8px;
padding: 16px;
box-shadow: var(--shadow-sm);
transition: all 200ms ease-out;
}
.card-hoverable:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
cursor: pointer;
}
.card-elevation-none {
box-shadow: none;
}
.card-elevation-sm {
box-shadow: var(--shadow-sm);
}
.card-elevation-md {
box-shadow: var(--shadow-md);
}
.card-elevation-lg {
box-shadow: var(--shadow-lg);
}Badge/Chip Component
interface ChipProps {
label: string;
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'neutral';
size?: 'small' | 'medium';
icon?: ReactNode;
onDelete?: () => void;
clickable?: boolean;
}Specifications:
.chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 12px;
border-radius: 16px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
transition: all 200ms ease-out;
}
.chip-primary {
background-color: rgba(76, 175, 80, 0.12);
color: var(--color-primary-dark);
}
.chip-small {
padding: 2px 8px;
font-size: 0.625rem;
}
.chip-clickable:hover {
opacity: 0.8;
cursor: pointer;
}Avatar Component
interface AvatarProps {
src?: string;
alt: string;
size?: 'small' | 'medium' | 'large' | 'xlarge';
fallback?: string; // Initials
online?: boolean;
}Specifications:
.avatar {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
background-color: var(--color-primary);
color: white;
font-weight: 600;
}
.avatar-small {
width: 32px;
height: 32px;
font-size: 0.75rem;
}
.avatar-medium {
width: 48px;
height: 48px;
font-size: 1rem;
}
.avatar-large {
width: 64px;
height: 64px;
font-size: 1.25rem;
}
.avatar-xlarge {
width: 96px;
height: 96px;
font-size: 1.75rem;
}
.avatar-online-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 25%;
height: 25%;
border-radius: 50%;
background-color: var(--color-success);
border: 2px solid var(--color-background);
}Layout Patterns
Mobile App Layout
┌─────────────────────────┐
│ Top App Bar │ Height: 56px
├─────────────────────────┤
│ │
│ │
│ Main Content Area │ Scrollable
│ │
│ │
├─────────────────────────┤
│ Bottom Navigation │ Height: 56px
└─────────────────────────┘Top App Bar:
- Height: 56px
- Padding: 16px horizontal
- Logo/Title on left
- Actions on right
Content Area:
- Padding: 16px all sides
- Safe area insets respected
- Max width for readability on tablets
Bottom Navigation:
- Height: 56px
- 3-5 primary actions
- Icons + labels
- Active state: Primary green
Spacing System
Use consistent spacing throughout:
Extra Small: 4px (spacing-1)
Small: 8px (spacing-2)
Medium: 16px (spacing-4)
Large: 24px (spacing-6)
Extra Large: 32px (spacing-8)Card/Section Spacing:
- Internal padding: 16px
- Gap between elements: 12px
- Gap between sections: 24px
Accessibility Implementation
Focus Indicators
/* Default focus ring */
*:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
border-radius: 2px;
}
/* Custom focus for specific components */
.button:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}ARIA Patterns
Button:
<button
aria-label="Help your neighbor Maria"
aria-disabled={disabled}
aria-busy={loading}
>
{children}
</button>Input:
<div>
<label htmlFor="task-description">
Task Description
</label>
<input
id="task-description"
aria-describedby="task-description-hint"
aria-invalid={hasError}
aria-required={required}
/>
<span id="task-description-hint">
Describe what help you need
</span>
</div>Screen Reader Text
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}Platform-Specific Considerations
iOS
// Use iOS native components where appropriate
import {
SafeAreaView,
StatusBar,
Platform
} from 'react-native';
// Respect iOS safe area
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
{content}
</SafeAreaView>
// iOS-specific styling
const styles = StyleSheet.create({
button: {
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 2,
},
}),
},
});Android
// Material Design 3 compliance
import { MD3LightTheme, MD3DarkTheme } from 'react-native-paper';
const theme = {
...MD3LightTheme,
colors: {
...MD3LightTheme.colors,
primary: '#4CAF50',
secondary: '#2196F3',
// ... other Good Deeds colors
},
};Web
/* Responsive design */
@media (max-width: 640px) {
.container {
padding: 16px;
}
}
@media (min-width: 641px) and (max-width: 1024px) {
.container {
padding: 24px;
max-width: 768px;
margin: 0 auto;
}
}
@media (min-width: 1025px) {
.container {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
}Animation Guidelines
Micro-interactions
/* Button press feedback */
.button {
transition: all 200ms cubic-bezier(0, 0, 0.2, 1);
}
.button:active {
transform: scale(0.98);
}
/* Loading states */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading-spinner {
animation: spin 1s linear infinite;
}
/* Fade in */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fadeIn 300ms ease-out;
}
/* Slide up */
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.slide-up {
animation: slideUp 300ms ease-out;
}Respect Reduced Motion
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}Implementation Checklist
When implementing a new feature or component:
Design
- [ ] Follows brand guidelines
- [ ] Uses design tokens
- [ ] Responsive across breakpoints
- [ ] Dark mode considered (if applicable)
Accessibility
- [ ] WCAG AA compliant color contrast
- [ ] Keyboard navigable
- [ ] Screen reader friendly
- [ ] Focus indicators visible
- [ ] Touch targets ≥ 44x44px
- [ ] Reduced motion respected
Code Quality
- [ ] Type-safe (TypeScript)
- [ ] Properly documented
- [ ] Unit tests included
- [ ] Performance optimized
- [ ] Error states handled
Platform
- [ ] Works on iOS
- [ ] Works on Android
- [ ] Works on Web (if applicable)
- [ ] Platform-specific patterns respected
Tools & Resources
Design Tools
- Figma: Component library and design files
- Storybook: Component documentation and testing
- Chromatic: Visual regression testing
Development Tools
- TypeScript: Type safety
- ESLint: Code linting
- Prettier: Code formatting
- Jest: Unit testing
- Axe: Accessibility testing
Fonts
- Inter: Google Fonts
- Poppins: Google Fonts
Icons
- Material Design Icons (Rounded): Material Icons
Version Control
Current Version: 1.0.0
Changelog:
- v1.0.0 (June 2026): Initial design system release
Support
For questions or contributions to the design system:
- Internal: Design System Team
- GitHub: [Repository Issues]
- Slack: #design-system
This design system is maintained by the Good Deeds design and engineering teams. Contributions and feedback are welcome.
