| name | frontend-design |
| description | Create distinctive, production-grade frontend interfaces, components, and widgets for the Clue Symptom Tracker. Use for `web-app` UI work in Next.js, React, TypeScript, and Tailwind when designing or refining cards, forms, pickers, chat surfaces, and compact health-tracking widgets for chronic illness users. Enforce Podia-inspired warmth, energy-conscious interaction design, and polished stateful component behavior. |
| license | Complete terms in LICENSE.txt |
This skill guides creation of distinctive, production-grade frontend interfaces for Clue. The design language is Podia-inspired: warm, approachable, and energy-conscious for users with chronic illness and brain fog.
Primary implementation target: web-app using Next.js, React, TypeScript, and Tailwind. Do not assume React Native or NativeWind unless the task explicitly asks for it.
Architecture note (agent-driven UI)
- Why this matters: In
web-app, some “conversion UI” components (e.g. ValuePropScreen) are tightly coupled to onboarding agent output types and are rendered from the “summary” step after Q3.
- Rule of thumb: If a React component is user-facing UI, prefer placing it under a UI-oriented folder (
src/components/...) or a clearly named neutral folder like src/agent-ui/... rather than src/backend/... to avoid misleading boundaries.
Design System Reference
Color Palette
primary: "#20132E"
"primary-light": "#f3f0fa"
"background-light": "#FDFBF9"
"card-light": "#FFFFFF"
"surface-light": "#FFFFFF"
"accent-peach": "#E8974F"
"accent-blue": "#A4C8D8"
"accent-purple": "#D0BDF4"
"accent-mint": "#B8E3D6"
"pill-hover": "#F3EFFF"
"pill-selected": "#E0D4FC"
"text-main": "#20132E"
"text-muted": "#554b66"
Typography
| Role | Font | Weights | Usage |
|---|
| Display | Fraunces (serif) | 300, 600 | Headings, onboarding titles |
| Body | Inter / DM Sans | 400, 500, 600, 700 | UI text, buttons, body copy |
Headlines: Large (34-40px), semibold Fraunces, tight leading
Body: 16-18px Inter/DM Sans, relaxed leading
Labels: 12-14px Inter, uppercase tracking for section labels
Spacing & Radius
DEFAULT: "12px"
xl: "16px"
"2xl": "24px"
"3xl": "32px"
pill: "9999px"
soft: "0 4px 20px -2px rgba(0, 0, 0, 0.05)"
card: "0 2px 12px -2px rgba(32, 20, 69, 0.05)"
Visual Elements
Decorative Blobs (positioned absolutely, pointer-events-none):
<View className="absolute -top-10 -right-10 w-40 h-40 bg-accent-peach/80 rounded-[3rem] rotate-12 blur-sm" />
<View className="absolute top-1/4 -left-12 w-32 h-32 bg-accent-blue/60 rounded-full blur-xl" />
<View className="absolute bottom-20 -right-6 w-48 h-48 bg-accent-purple/50 rounded-[4rem] -rotate-12" />
Bottom Gradient Overlay (for sticky CTAs):
<View className="bg-gradient-to-t from-background-light via-background-light to-transparent pt-12" />
Component Patterns
Compact Widget Rulebook
Use these rules for quick-entry cards, inline trackers, chat widgets, and other compact interactive UI:
- Prefer one self-contained inner surface instead of stacking extra outer card chrome around a small control.
- Make controls content-hugging by default. Do not let selects, pills, or badges stretch unless the layout truly benefits from it.
- For stubborn native selects, render a visible text-plus-chevron shell and use an invisible select overlay only for interaction.
- Keep left and right padding visually balanced. Do not fake symmetry with absolute arrow positioning that steals one side's space.
- In dense rows, let metadata controls hug content and let the primary CTA take the remaining width.
- Tighten internal spacing before shrinking tap targets.
- For medication trackers, keep one visible row per medication name, keep dose chips and the add button on one horizontal strip, and anchor any total summary pill on the far right instead of letting controls wrap into a second row.
- If the user says "nothing changed", stop nudging the same CSS assumption and change the layout structure causing the problem.
Stateful CTA Pattern
For widgets that require an explicit user choice before committing data:
- Show a disabled CTA by default.
- Enable it only after an explicit user action, not because a default value exists or previously saved data was loaded.
- Make the enabled label specific to the pending action.
- Good:
Log 6/10 at 11:51 PM
- Bad: generic
Done when the button is actually committing data.
- Prefer a strong enabled state: solid primary background, white text, and a check icon when the action is ready.
- Disabled state should look intentionally unavailable, not broken.
Draft vs Persisted State
When a widget mixes supporting fields with one decisive primary input:
- Keep draft sub-state local until the decisive action happens.
- Do not auto-create a structured payload just because the user typed context or changed a time picker.
- A note field or time control should not silently imply a selected rating, symptom, or answer.
- Persist supporting fields once a real primary choice exists.
Logging Widget Structure
For health-tracking widgets like mood, symptoms, or factors:
- Put optional metadata like time in the header where users already scan.
- Put the freeform context field below the primary control.
- Put the final CTA below supporting inputs unless an inline action clearly reads better.
- Use neutral, non-judgmental copy for the control labels and state names.
Pill Chips (Selection)
<Pressable className="px-5 py-3 bg-white border border-gray-200 rounded-full shadow-sm">
<Text className="text-gray-700 font-medium">Option</Text>
</Pressable>
<Pressable className="px-5 py-3 bg-pill-selected border border-primary rounded-full">
<Text className="text-primary font-semibold">Option</Text>
<Icon name="check" size={16} />
</Pressable>
Cards with Icons
<Pressable className="flex-row items-center p-4 bg-white border-2 border-transparent rounded-2xl shadow-soft">
{}
<View className="w-12 h-12 rounded-xl bg-yellow-100 items-center justify-center mr-4">
<Icon name="bolt" color="#CA8A04" size={24} />
</View>
<View className="flex-1">
<Text className="font-semibold text-lg text-primary">Title</Text>
<Text className="text-sm text-gray-500">Subtitle description</Text>
</View>
{}
<View className="w-6 h-6 rounded-full border-2 border-gray-300" />
</Pressable>
Chat Bubbles
<View className="p-6 rounded-[2rem] rounded-bl-none bg-white border border-black/5 shadow-card">
<Text className="text-[17px] leading-relaxed font-medium text-primary">
Message content
</Text>
</View>
<View className="p-6 rounded-[2rem] rounded-br-none bg-primary">
<Text className="text-[17px] leading-relaxed font-medium text-white/95">
Message content
</Text>
</View>
Focus Card (Hypothesis Tracking)
<View className="rounded-[2.5rem] border border-primary/5 bg-white p-7 shadow-soft">
{}
<View className="flex-row items-center px-4 py-1.5 rounded-full bg-accent-mint/40">
<Icon name="push-pin" size={18} className="text-teal-800" />
<Text className="text-xs font-bold uppercase tracking-wider text-teal-900">
Focus
</Text>
</View>
{}
<Text className="text-xl font-bold text-primary">
How does{' '}
<Text className="bg-accent-purple/30 px-2 rounded-lg">Poor Sleep</Text>{' '}
impact{' '}
<Text className="bg-accent-peach/40 px-2 rounded-lg text-orange-900">
Joint Pain
</Text>
?
</Text>
{}
<View className="h-3 w-full rounded-full bg-primary-light overflow-hidden">
<View className="h-full w-[65%] rounded-full bg-accent-purple" />
</View>
</View>
Primary Button (Sticky CTA)
<Pressable className="w-full py-4 bg-primary rounded-full shadow-lg flex-row items-center justify-center">
<Text className="text-white text-lg font-bold">Continue</Text>
<Icon name="arrow-forward" color="white" className="ml-2" />
</Pressable>
Energy-Conscious UX Principles
- Large touch targets: Minimum 48px, preferably 56px for primary actions
- One-tap actions: Prefer toggles and chips over text input
- Progressive disclosure: Show minimal UI upfront, expand on demand
- Gentle transitions: Soft animations, no jarring movements
- Clear hierarchy: One primary CTA per screen, secondary actions subtle
- Forgiving interactions: Easy to undo, skip, or "do later"
- Content-hugging metadata: Small controls like time, status, or filters should usually hug content instead of stretching across the row
- Explicit intent before save: A widget should not appear "ready to log" until the user has made the primary selection
Screen Structure
<SafeAreaView className="flex-1 bg-background-light">
{}
<DecorativeBlobs />
{}
<View className="flex-1 px-6 pt-12">
{}
<ProgressBar step={1} total={3} />
{}
<Text className="font-display text-4xl font-semibold text-primary mb-3">
Screen Title
</Text>
{}
<Text className="text-gray-600 text-lg leading-relaxed mb-8">
Helpful description
</Text>
{}
<ScrollView className="flex-1" showsVerticalScrollIndicator={false}>
{}
</ScrollView>
</View>
{}
<View className="p-6 bg-gradient-to-t from-background-light via-background-light to-transparent">
<PrimaryButton title="Continue" />
</View>
</SafeAreaView>
UI Source References
Hero Pattern: Visible-Style Persona Layout (Landing Pages)
Inspiration: makevisible.com — Clean, trustworthy, human-centered design.
Use for: Marketing landing pages, not in-app screens.
Desktop Layout (min-width: 768px)
┌─────────────────────────────────────────────────────────────────────┐
│ SOLID BACKGROUND COLOR (accent-blue: #A4C8D8) │
│ ┌──────────────────────────┐ ┌─────────────────────────────────┐│
│ │ LEFT 45-50% │ │ RIGHT 50-55% ││
│ │ ───────────── │ │ ───────────── ││
│ │ H1: Fraunces, white │ │ ││
│ │ "Your flares have │ │ [PERSONA IMAGE] ││
│ │ a forecast." │ │ PNG cutout on solid bg ││
│ │ │ │ Position: right edge, ││
│ │ H2: DM Sans, white/80% │ │ bleeds off container ││
│ │ "Get a 48-hour..." │ │ ││
│ │ │ │ object-position: top right ││
│ │ [Condition Pills] │ │ max-height: 90vh ││
│ │ white borders, semi- │ │ ││
│ │ transparent bg │ │ ││
│ │ │ │ ││
│ │ [CTA Button →] │ │ ││
│ │ White bg, dark text │ │ ││
│ └──────────────────────────┘ └─────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────┘
Mobile Layout (max-width: 767px)
Option A: Card Style (Visible's iPhone screenshot)
┌─────────────────────────────────┐
│ CARD with rounded corners │
│ (inside a cream background) │
│ ───────────────────────── │
│ H1 (centered) │
│ H2 (centered) │
│ │
│ [Condition Pills] │
│ (wrapped, centered) │
│ │
│ [CTA Button →] │
│ │
│ [PERSONA IMAGE] │
│ (below text, centered, │
│ smaller, full visible) │
└─────────────────────────────────┘
Option B: Full-Width Background (alternate variant)
┌─────────────────────────────────┐
│ FULL BLEED BACKGROUND │
│ Persona as subtle bg image │
│ (opacity 20%, positioned │
│ bottom-right, fixed) │
│ ───────────────────────── │
│ H1 (centered) │
│ H2 (centered) │
│ [Pills] [CTA] │
└─────────────────────────────────┘
CSS Implementation
.hero-visible {
background-color: #a4c8d8;
min-height: 100vh;
position: relative;
overflow: hidden;
}
@media (min-width: 768px) {
.hero-persona {
position: absolute;
right: 0;
bottom: 0;
height: 90vh;
max-height: 800px;
width: auto;
object-fit: contain;
object-position: bottom right;
transform: translateX(5%);
}
}
@media (max-width: 767px) {
.hero-visible {
background-color: #fdfbf9;
padding: 1rem;
}
.hero-card {
background-color: #a4c8d8;
border-radius: 2rem;
padding: 2rem 1.5rem;
overflow: hidden;
}
.hero-persona {
position: relative;
width: 80%;
max-width: 300px;
margin: 2rem auto 0;
display: block;
}
}
.condition-pill {
padding: 0.625rem 1.25rem;
border-radius: 9999px;
border: 1px solid rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.15);
color: white;
font-weight: 500;
font-size: 0.875rem;
backdrop-filter: blur(4px);
}
.hero-cta {
background: white;
color: #20132e;
padding: 1rem 2rem;
border-radius: 9999px;
font-weight: 700;
font-size: 1.125rem;
box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.15);
transition:
transform 0.2s,
box-shadow 0.2s;
}
.hero-cta:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px -4px rgba(0, 0, 0, 0.2);
}
Key Visual Properties
| Element | Desktop | Mobile (Card) |
|---|
| Background | Solid #A4C8D8 full-bleed | Cream outer, card has color |
| Container | max-w-7xl mx-auto | rounded-[2rem] card |
| Text align | Left | Center |
| Image size | 90vh height, auto width | 80% width, centered |
| Image position | Absolute, right edge bleed | Relative, below text |
| CTA | Inline with text | Full width or centered |
A/B Testing Support
<h1 data-variant="default">Your flares have a forecast.</h1>
<h1 data-variant="alt-1" class="hidden">See flares coming 48 hours early.</h1>
<h1 data-variant="alt-2" class="hidden">Stop being blindsided by flares.</h1>
<script>
const variant =
new URLSearchParams(location.search).get('headline') || 'default';
document.querySelectorAll('h1[data-variant]').forEach((el) => {
el.classList.toggle('hidden', el.dataset.variant !== variant);
});
</script>
UTM-Based Condition Pills
const utmContent = new URLSearchParams(location.search).get('utm_content');
const conditionMap = {
fibromyalgia: ['Fibromyalgia', 'Chronic Pain', 'ME/CFS'],
long_covid: ['Long COVID', 'Post-Viral', 'ME/CFS'],
endo: ['Endometriosis', 'PCOS', 'Chronic Pain'],
default: ['Long COVID', 'ME/CFS', 'Fibromyalgia', 'POTS', 'EDS'],
};
const conditions = conditionMap[utmContent] || conditionMap.default;
Spoon Saver Landing Page Patterns
Product Direction: "Low-effort, non-judgmental, flexible symptom tracking that works on bad days and doesn't create guilt."
Testimonial Cards (Social Proof)
<div className="hover-lift group rounded-[2rem] border border-primary/5 bg-white p-6 shadow-soft">
{}
<div className="mb-4 inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium bg-accent-purple/30 text-purple-800">
<span className="size-1.5 rounded-full bg-current" />
Feeling judged
</div>
{}
<blockquote className="font-display text-primary mb-4 text-lg leading-relaxed">
"Quote from user"
</blockquote>
{}
<div className="flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-full bg-accent-purple/20">
<Icon name="person" className="text-primary/60" size="sm" />
</div>
<div>
<p className="text-sm font-medium text-primary">r/ChronicIllness</p>
<p className="text-xs text-text-muted">Fibromyalgia</p>
</div>
</div>
</div>
Pain Point Color Mapping:
| Pain Point | Color Class |
|---|
| burden | bg-accent-rose/20 text-red-700 |
| judgment | bg-accent-purple/30 text-purple-800 |
| brain_fog | bg-accent-blue/30 text-blue-800 |
| relief | bg-accent-mint/40 text-teal-800 |
| aspiration | bg-accent-peach/40 text-orange-800 |
| validation | bg-accent-mint/30 text-teal-700 |
"No Guilt" Comparison Cards
<div className="rounded-2xl border-2 border-red-200 bg-red-50/50 p-6">
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-red-600">
Other Apps
</p>
<div className="flex items-center gap-3">
<Icon name="sentiment_dissatisfied" size="lg" className="text-red-500" />
<span className="text-sm text-red-700">Pain Level: 8/10 - AWFUL</span>
</div>
<div className="mt-2 flex items-center gap-2 rounded-lg bg-red-100 p-2 text-sm text-red-700">
<Icon name="warning" size="sm" className="text-red-500" />
You broke your 14-day streak!
</div>
</div>
<div className="rounded-2xl border-2 border-accent-mint bg-accent-mint/10 p-6 shadow-soft">
<p className="mb-3 text-sm font-semibold uppercase tracking-wide text-teal-700">
Chronic Life
</p>
<div className="flex items-center gap-3">
<div className="flex size-8 items-center justify-center rounded-full bg-accent-purple/20">
<Icon name="circle" size="sm" className="text-primary/60" />
</div>
<span className="text-sm text-primary">Intensity: 8/10 - High intensity</span>
</div>
<div className="mt-2 flex items-center gap-2 rounded-lg bg-accent-mint/30 p-2 text-sm text-teal-800">
<Icon name="check_circle" size="sm" className="text-teal-600" />
Logged. Tough day — hope you can rest.
</div>
</div>
Section Badge Pattern
<div className="mb-6 inline-flex items-center gap-2 rounded-full bg-accent-peach/30 px-4 py-2 text-sm font-semibold text-primary">
<Icon name="groups" size="sm" />
From the Spoonie Community
</div>
Onboarding Q3 Widget Patterns
Based on onboarding-flow.json, use these widget types:
<div className="flex items-center gap-4">
<div className="h-16 w-8 rounded-full border-2 border-primary/20 overflow-hidden">
<div
className="w-full bg-accent-purple transition-all"
style={{ height: `${value}%` }}
/>
</div>
<span className="text-lg font-medium text-primary">{value}%</span>
</div>
<div className="flex gap-3">
{['green', 'yellow', 'red'].map(color => (
<button className={`
size-16 rounded-xl transition-all
${selected === color ? 'ring-2 ring-primary scale-110' : ''}
${color === 'green' && 'bg-green-400'}
${color === 'yellow' && 'bg-yellow-400'}
${color === 'red' && 'bg-red-400'}
`}>
{color === 'green' && 'Go'}
{color === 'yellow' && 'Caution'}
{color === 'red' && 'Cancel'}
</button>
))}
</div>
<div className="flex flex-wrap gap-2">
{options.map(opt => (
<button className={`
px-4 py-2 rounded-full border transition-all
${selected.includes(opt.value)
? 'bg-pill-selected border-primary text-primary font-semibold'
: 'bg-white border-gray-200 text-gray-700'}
`}>
{opt.label}
</button>
))}
</div>
Memory Stream Visualization (Brain Fog)
<div className="rounded-2xl rounded-bl-none border border-primary/5 bg-accent-blue/5 p-4">
<p className="text-primary leading-relaxed">
You logged{' '}
<span className="rounded bg-accent-purple/20 px-1.5 py-0.5 font-medium">
Poor Sleep
</span>{' '}
on Tuesday. Is today's{' '}
<span className="rounded bg-accent-rose/20 px-1.5 py-0.5 font-medium">
migraine
</span>{' '}
related?
</p>
</div>;
{
}
<div className="mt-4 flex items-center gap-2 rounded-lg bg-accent-mint/20 p-3">
<Icon name="insights" size="sm" className="text-teal-600" />
<span className="text-sm font-medium text-teal-800">
Pattern Detected: 70% of your migraines follow poor sleep by 48 hours
</span>
</div>;
Anti-Patterns (AVOID)
CRITICAL: No Emojis
- ❌ NEVER use emojis in UI, code, or content — Emojis feel juvenile, inconsistent across platforms, and clash with our sophisticated Podia-inspired aesthetic. Use icons from our icon system instead.
General Design Anti-Patterns
- ❌ Generic fonts (Inter alone is fine, but pair with Fraunces for display)
- ❌ Pure white backgrounds (use warm cream #FDFBF9)
- ❌ Sharp corners on cards (always round 16px+)
- ❌ Heavy drop shadows (use soft, low-opacity shadows)
- ❌ Dense forms or multiple text inputs per screen
- ❌ Purple gradients on white (use warm pastels instead)
- ❌ Aggressive animations or transitions
- ❌ Small touch targets (minimum 48px)
- ❌ Stock photos of smiling people (use authentic, hopeful imagery)
- ❌ Abstract illustrations instead of product UI mockups
- ❌ Rainbow gradients (stick to single accent color per section)
Chronic Illness UX Anti-Patterns (CRITICAL)
- ❌ Judgmental iconography: Sad faces 😢, red angry faces, "awful" labels
- ❌ Streak gamification: "You broke your streak!" messages
- ❌ Color-coded judgment: Red = bad day (use neutral intensity scales instead)
- ❌ Guilt-inducing language: "You missed 3 days" without empathy
- ❌ Complex navigation: Nested menus requiring multiple taps
- ❌ Required daily logging: Must allow gaps without penalty
- ❌ Text-heavy inputs: Typing is hard with brain fog, use taps
- ❌ Bright flashy UI: Painful during migraines or sensory sensitivity
- ❌ Social feeds in tracking: Users want focus, not distraction
- ❌ One-size-fits-all scales: "6/10 is bad" — but for chronic pain, 6 might be a GOOD day
Language Anti-Patterns (Per Language of Care guidelines)
- ❌ "Bad day" → Use "High intensity day" (neutral)
- ❌ "You should..." → Use "You might try..." (permission)
- ❌ "Track everything" → Use "Track what matters to YOUR question"
- ❌ Generic messages → Personalize with user's context
- ❌ Clinical jargon without context → Use accessible language