OffgridMobile follows a brutalist, minimal design system inspired by terminal aesthetics and focused on functionality over decoration. The interface emphasizes clarity, efficiency, and respect for the user's attention.
- No unnecessary decoration or embellishment
- Every element serves a purpose
- Remove before adding
- Silence over noise
- Monospace typography (
Menlo) throughout - Dark mode: Pure black background (
#0A0A0A), light mode: clean white (#FFFFFF) - Single accent color (emerald green —
#34D399dark,#059669light) - High contrast for readability in both themes
- Crisp borders and sharp edges
- Use centralized design tokens (typography, spacing, colors)
- No magic numbers in component styles
- Maintain visual hierarchy through size and weight, not color
- Predictable interactions
- Respect screen real estate
- Compact layouts where appropriate
- Progressive disclosure (hide complexity until needed)
- Metadata as "whispers" not shouts
- Lightweight components
- No unnecessary animations (functional animations only)
- Fast, responsive interactions
- Optimize for lower-end devices
All text uses Menlo (monospace) for consistency and readability.
| Token | Size | Weight | Use Case | Example |
|---|---|---|---|---|
TYPOGRAPHY.display |
22px | 200 | Hero numbers, RAM display | "8.2 GB" |
TYPOGRAPHY.h1 |
24px | 300 | Screen titles | "Conversations" |
TYPOGRAPHY.h2 |
16px | 400 | Section headers | "Downloaded Models" |
TYPOGRAPHY.h3 |
13px | 400 | Subsection headers | "Text Models" |
TYPOGRAPHY.body |
14px | 400 | Primary body text | Message content |
TYPOGRAPHY.bodySmall |
13px | 400 | Secondary body text | Descriptions |
TYPOGRAPHY.label |
10px | 400 | Labels, whispers | "ACTIVE MODEL" |
TYPOGRAPHY.labelSmall |
9px | 400 | Tiny labels | Badges |
TYPOGRAPHY.meta |
10px | 300 | Metadata | "12.3 tok/s" |
TYPOGRAPHY.metaSmall |
9px | 300 | Tiny metadata | Timestamps |
Typography Rules:
- Use
displayfor large numeric displays (memory, performance metrics) - Use
h1for screen titles - Use
h2for major sections - Use
h3for subsections within cards - Use
labelfor uppercase labels and section markers ("whispers") - Use
metafor secondary information (timestamps, performance data) - Maintain consistent letter spacing: tight (-0.5) for large text, loose (+0.3) for small labels
Use the spacing scale for all margins, paddings, and gaps.
| Token | Value | Use Case |
|---|---|---|
SPACING.xs |
4px | Tight spacing, icon gaps |
SPACING.sm |
8px | Small padding, list item gaps |
SPACING.md |
12px | Standard padding |
SPACING.lg |
16px | Card padding, section spacing |
SPACING.xl |
24px | Screen padding, major sections |
SPACING.xxl |
32px | Large screen padding, hero sections |
Spacing Rules:
- Screen-level padding:
SPACING.xl(24px) - Card padding:
SPACING.lg(16px) - Button padding:
SPACING.smorSPACING.md - List item gaps:
SPACING.sm - Icon-to-text gaps:
SPACING.sm - Section breaks:
SPACING.xlorSPACING.xxl
Monochromatic palette with emerald green as the only accent. Supports light and dark modes via the theme system (src/theme/).
Colors are accessed via useTheme() hook — not imported directly. All tokens are available as colors.xxx in both themes.
| Token | Dark | Light | Use Case |
|---|---|---|---|
colors.primary |
#34D399 |
#059669 |
Main accent, active states |
colors.background |
#0A0A0A |
#FFFFFF |
App background |
colors.surface |
#141414 |
#F5F5F5 |
Cards, elevated elements |
colors.surfaceLight |
#1E1E1E |
#EBEBEB |
Nested elements, inputs |
colors.text |
#FFFFFF |
#0A0A0A |
Primary text |
colors.textSecondary |
#B0B0B0 |
#525252 |
Secondary text |
colors.textMuted |
#808080 |
#8A8A8A |
Metadata, placeholders |
colors.border |
#1E1E1E |
#E5E5E5 |
Default borders |
colors.borderFocus |
#34D399 |
#059669 |
Focused/active borders |
colors.error |
#EF4444 |
#DC2626 |
Error states |
colors.overlay |
rgba(0,0,0,0.7) |
rgba(0,0,0,0.4) |
Modal backgrounds |
| Level | Light Mode | Dark Mode |
|---|---|---|
shadows.small |
Black, opacity 0.15, radius 6 | White, opacity 0.08, radius 1 |
shadows.medium |
Black, opacity 0.22, radius 10 | White, opacity 0.10, radius 2 |
shadows.large |
Black, opacity 0.35, radius 18 | White, opacity 0.12, radius 3 |
shadows.glow |
Emerald, opacity 0.25, radius 12 | Emerald, opacity 0.15, radius 4 |
Dark mode shadows use very tight radius (1-3px) to preserve crisp card edges while still providing subtle elevation.
Color Rules:
- Use emerald (
primary) sparingly — only for active states, focus, and important actions - Prefer monochrome hierarchy over color variation
- Semantic colors (success, warning, error) only for their intended purpose
- Borders should be subtle (
border) unless focused (borderFocus) - Backgrounds should use the three-tier system:
background→surface→surfaceLight - Never import COLORS directly — always use
useTheme()for dynamic theming
- Default: Transparent with border (outline style)
- Primary: Transparent with emerald border, emerald text
- Secondary: Transparent with gray border, white text
- Ghost: No border, just text
- Padding: Use spacing scale (
sm,md,lgfor different sizes) - Border radius:
8px(sharp, minimal) - No shadows or gradients
- Background:
colors.surface - Padding:
SPACING.lg - Border radius:
8px - Border:
1px solid colors.border - Shadow:
shadows.small(subtle elevation) - Hover state (if interactive):
backgroundColor: colors.surfaceHover - Compact variant:
ModelCardsupports acompactprop for dense list views — horizontal layout with content on left and icon actions (download, select, delete) on right. Used for recommended models and image model lists.
- Small circular touch targets with Feather icons (download, check-circle, trash-2)
- Transparent background,
colors.textMuteddefault,colors.primaryfor active/selected state hitSlopfor easier mobile tapping- Used in compact
ModelCardand list items instead of full-width text buttons
All screens must use a standardized header style for visual consistency. Two variants exist:
Tab Screen Header (top-level tabs: Chats, Projects, Models, Settings):
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: SPACING.lg,
paddingVertical: SPACING.md,
borderBottomWidth: 1,
borderBottomColor: colors.border,
backgroundColor: colors.surface,
...shadows.small,
zIndex: 1,
}- Title (
TYPOGRAPHY.h2) on the left - Optional action button on the right (e.g. "New", download icon)
backgroundColor: colors.surface+shadows.smallfor elevation
Sub-Screen Header (pushed screens: ModelSettings, Storage, Security, etc.):
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: SPACING.lg,
paddingVertical: SPACING.md,
borderBottomWidth: 1,
borderBottomColor: colors.border,
backgroundColor: colors.surface,
...shadows.small,
zIndex: 1,
gap: SPACING.md,
}- Back button on the left
- Title (
TYPOGRAPHY.h2,flex: 1) fills remaining space - Optional action button on the right
Key rules:
- Every header MUST have
backgroundColor: colors.surfaceand...shadows.small - Every header MUST have
zIndex: 1(so shadow renders above content below) - Padding is always
SPACING.lghorizontal,SPACING.mdvertical - Border bottom is always 1px
colors.border
Exceptions: Onboarding, LockScreen, and ModelDownload have no standard header (full-screen flows).
- Background:
colors.surfaceLight - Border:
1px solid colors.border - Focus border:
colors.borderFocus - Padding:
SPACING.md - Border radius:
8px - Monospace font
- Horizontal scroll of filter options (org, size, quantization, type)
- Default:
colors.surfaceLightbackground,colors.borderborder - Active:
colors.primaryborder,colors.primarytext TYPOGRAPHY.labelSmallorTYPOGRAPHY.metafor pill text- Expandable sections with multi-select for dimension filters (e.g., organizations)
- Clear all filters button when any filter is active
- Use
TYPOGRAPHY.labelorTYPOGRAPHY.labelSmall - Always uppercase (e.g., "ACTIVE MODEL", "DOWNLOADED MODELS")
- Color:
COLORS.textMuted(whisper, not shout) - Letter spacing:
+0.3for readability - Purpose: Visual anchors, not primary content
- Use
TYPOGRAPHY.metaorTYPOGRAPHY.metaSmall - Color:
COLORS.textSecondaryorCOLORS.textMuted - Parentheses for inline metadata:
(12.3 tok/s) - Bullets for lists:
• Metal • 99 layers - Keep concise: numbers + abbreviations
- Primary: Screen title (h1) or main content (body)
- Secondary: Section headers (h2, h3) or descriptions (bodySmall)
- Tertiary: Labels (label), metadata (meta), timestamps (metaSmall)
- Generous top-level spacing (
SPACING.xl,SPACING.xxl) - Tighter spacing within components (
SPACING.sm,SPACING.md) - Breathing room around text (
SPACING.mdminimum) - Avoid cramped layouts — let content breathe
| State | Visual Treatment |
|---|---|
| Default | Normal colors, subtle borders |
| Hover | COLORS.surfaceHover background |
| Active/Pressed | COLORS.primaryDark accent |
| Focused | COLORS.borderFocus border |
| Disabled | 40% opacity, COLORS.textDisabled |
| Loading | Subtle spinner, muted text |
- Success: Brief green flash or checkmark
- Error: Red text + error icon
- Loading: Minimal spinner (no elaborate animations)
- Empty states: Simple message + optional action button
❌ Colorful gradients or heavy shadows — Keep it flat; use theme shadows sparingly
❌ Multiple accent colors — Emerald only
❌ Rounded pill shapes — Use minimal 8px radius
❌ Decorative animations — Only functional animations (loading, state transitions)
❌ Mixed font families — Menlo only
❌ Heavy borders or 3D effects — Flat, sharp, minimal
❌ Large empty spaces without purpose — Dense when appropriate
❌ Color-coded information — Use hierarchy, not color
❌ Cluttered layouts — Remove before adding
Theme-independent tokens (TYPOGRAPHY, SPACING, FONTS) come from src/constants/index.ts.
Colors and shadows come from the theme system via hooks:
import { SPACING, TYPOGRAPHY } from '../constants';
import { useTheme, useThemedStyles } from '../theme';
import type { ThemeColors, ThemeShadows } from '../theme';
const MyScreen = () => {
const { colors } = useTheme();
const styles = useThemedStyles(createStyles);
return (
<View style={styles.container}>
<Text style={styles.title}>Hello</Text>
<Icon color={colors.textMuted} />
</View>
);
};
const createStyles = (colors: ThemeColors, shadows: ThemeShadows) => ({
container: {
backgroundColor: colors.background,
padding: SPACING.xl,
},
title: {
...TYPOGRAPHY.h1,
color: colors.text,
marginBottom: SPACING.md,
},
});When building or refactoring a component:
- Uses
useTheme()anduseThemedStyles()(no hardcoded colors) - Follows typography scale (
TYPOGRAPHY.*) - Uses spacing scale for all padding/margin (
SPACING.*) - Colors from theme only (
colors.*via hook, never direct import) - Labels are uppercase with proper typography
- Metadata uses appropriate meta styles
- Buttons follow button patterns (transparent + border)
- Cards use surface colors with minimal borders
- Shadows from theme only (
shadows.small/medium/large) - States (hover, focus, disabled) properly styled
- Accessible (sufficient contrast, touch targets ≥ 44px)
Before (decorative, colorful):
- Gradient background
- Multiple colors for badges
- Large shadows
- Rounded pill shapes
- Mixed fonts
After (brutalist, minimal):
- Flat
COLORS.surfacebackground - Single emerald accent for active state
- Sharp
8pxborder radius - Monospace font throughout
- Uppercase label for section ("DOWNLOADED MODELS")
- Metadata as small, muted text
- Subtle border, no shadow
- Compact mode: horizontal layout with icon action buttons (download, select, delete) on right
- Author shown as small pill-style tag, not a full line
Before:
- Bubble-style messages with gradients
- Colorful avatars
- Large padding, rounded corners
- Mixed fonts for metadata
After:
- Flat background (
COLORS.surface) - No avatars (text-first)
- Compact padding (
SPACING.md) - Sharp edges
- Metadata in tiny, muted monospace
- Role indicated by position and subtle color, not decoration
This design system was intentionally created to:
- Differentiate from typical chat apps (iOS Messages, WhatsApp)
- Signal technical capability through terminal aesthetics
- Respect privacy with dark, unobtrusive UI
- Optimize for content — let the AI responses shine
- Perform well on low-end devices (no heavy rendering)
- Age gracefully — minimalism doesn't go out of style
As the app evolves:
- Stay true to brutalism — add functionality, not decoration
- Maintain consistency — use tokens religiously
- Question additions — does this serve the user or just look nice?
- Optimize for density — information should be easy to scan
- Theme System:
src/theme/(palettes, hooks, style factory) - Design Tokens:
src/constants/index.ts(typography, spacing, fonts) - Codebase Guide:
docs/standards/CODEBASE_GUIDE.md - Typography: All Menlo, weights 200-400 only
- Inspiration: Terminal UIs, Linear, Vercel, brutalist web design
Remember: Silence, clarity, and function over form. Let the AI's capabilities speak, not the interface.