Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
User journey map with SVG emotion curve, phase grid, and cross-references to personas and stories.
A web component that renders a complete user journey map with two visual layers: an SVG emotion curve that plots the user's emotional state across phases, and a detail grid showing actions, thoughts, touchpoints, pain points, and opportunities for each phase. Supports JSON loading via src attribute or programmatic data via the .phases property.
<user-journey persona="Sarah Chen" persona-id="persona-sarah-chen" story-ids="PROJ-142,PROJ-156" src="/data/journey-api-evaluation.json"> <h2 slot="title">API Platform Evaluation</h2> <p slot="summary">How a developer discovers, evaluates, and adopts our platform</p></user-journey>
| Attribute | Type | Default | Description |
|---|---|---|---|
persona |
string | — | Name of the persona taking this journey |
persona-id |
string | — | ID of a <user-persona> element to link to (renders as an anchor) |
story-ids |
string (comma-separated) | — | Related story IDs shown as chip links in the header (e.g., "PROJ-142,PROJ-156") |
src |
string (URL) | — | Path to a JSON file containing the full journey data including phases |
compact |
boolean | — | Reduces padding, font sizes, curve height, and grid cell widths |
Content values — title and summary — are provided as named slots. State attributes like persona, persona-id, and story-ids remain on the element.
| Slot | Expected Content | Description |
|---|---|---|
title |
<h2> or text |
Journey map heading. Defaults to "User Journey" when omitted. |
summary |
<p> or text |
Brief description shown below the title. |
<user-journey persona="Sarah Chen" persona-id="persona-sarah-chen" src="/data/journey.json"> <h2 slot="title">API Platform Evaluation</h2> <p slot="summary">How a developer discovers, evaluates, and adopts our platform</p></user-journey>
Phase data can be loaded from a JSON file via the src attribute or set programmatically via the .phases property. The JSON schema supports top-level metadata plus an array of phase objects.
{ "title": "API Platform Evaluation", "persona": "Sarah Chen", "personaId": "persona-sarah-chen", "summary": "How a developer discovers, evaluates, and adopts our platform", "phases": [ { "name": "Awareness", "emotion": "curious", "storyIds": ["PROJ-142"], "actions": ["Searches for solutions", "Reads comparison articles"], "thoughts": ["There must be a better way"], "touchpoints": ["Google", "Hacker News"], "painPoints": ["Too many options to compare"], "opportunities": ["Clear, developer-focused landing page"] }, { "name": "Evaluation", "emotion": "hopeful", "storyIds": ["PROJ-156"], "actions": ["Reads documentation", "Tries quick-start guide"], "thoughts": ["This looks promising"], "touchpoints": ["Docs site", "GitHub"], "painPoints": ["Missing advanced examples"], "opportunities": ["Interactive code playground"] } ]}
Each phase object supports these fields:
| Field | Type | Description |
|---|---|---|
name |
string | Phase heading (e.g., "Awareness", "Evaluation") |
emotion |
string | One of the 9 emotion values (see Emotion Scale below) |
storyIds |
string[] | Related user story IDs shown as chips in the phase header |
actions |
string[] | What the user does during this phase |
thoughts |
string[] | What the user thinks or feels |
touchpoints |
string[] | Channels and interfaces encountered |
painPoints |
string[] | Friction points and problems. Cells tinted red. |
opportunities |
string[] | Improvement ideas and design opportunities. Cells tinted green. |
The emotion field in each phase drives the Y-position of the SVG curve dot and its color. Nine values are supported, ranging from positive to negative.
| Value | Emoji | Score | Color |
|---|---|---|---|
delighted |
😄 | 0.95 | #16a34a |
satisfied |
😊 | 0.80 | #22c55e |
hopeful |
🙂 | 0.68 | #84cc16 |
curious |
🤔 | 0.55 | #eab308 |
neutral |
😐 | 0.50 | #94a3b8 |
uncertain |
😕 | 0.40 | #f97316 |
confused |
😵 | 0.30 | #fb923c |
frustrated |
😤 | 0.18 | #ef4444 |
angry |
😠 | 0.05 | #dc2626 |
The curve is rendered as an SVG with Bezier curves connecting each phase dot. The area under the curve uses a gradient fill. Three horizontal zones (green/yellow/red) provide visual context for positive, neutral, and negative emotional ranges.
Add the compact attribute for tighter layouts with reduced padding, smaller fonts, and a shorter emotion curve. Useful for dashboards or pages with multiple journey maps.
<user-journey persona="Sarah Chen" compact> <h2 slot="title">Support Incident Journey</h2> <p slot="summary">From first alert to resolved incident</p></user-journey>
For dynamic applications, set the .phases property directly instead of using a JSON file. The component re-renders automatically when phases are assigned.
const journey = document.querySelector('user-journey'); journey.phases = [ { name: 'Discovery', emotion: 'curious', actions: ['Browses landing page'], thoughts: ['Looks interesting'], touchpoints: ['Website'], painPoints: [], opportunities: ['Add video walkthrough'] }, { name: 'Onboarding', emotion: 'hopeful', actions: ['Completes setup wizard'], thoughts: ['This is straightforward'], touchpoints: ['App', 'Email'], painPoints: ['Too many steps'], opportunities: ['Reduce to 3 steps'] }, { name: 'First Success', emotion: 'delighted', actions: ['Completes first task'], thoughts: ['This actually works!'], touchpoints: ['App'], painPoints: [], opportunities: ['Celebrate with confetti'] }];
The <user-journey> component is designed to link to related <user-persona> and <user-story> elements on the same page:
persona-id to the id of a <user-persona> element. The persona name renders as an anchor that scrolls to that element.story-ids to a comma-separated list of story IDs. Each ID renders as a chip link. Per-phase storyIds in the JSON data also render as chips in the phase header row.<user-persona id="persona-sarah-chen" role="Product Manager"> <h2 slot="name">Sarah Chen</h2> <p slot="bio">Experienced PM focused on developer tools.</p></user-persona> <user-story story-id="PROJ-142" priority="high" status="in-progress" points="5"> <span slot="persona">Sarah Chen</span> <span slot="action">view project timelines in one dashboard</span></user-story> <user-journey persona="Sarah Chen" persona-id="persona-sarah-chen" story-ids="PROJ-142" src="/data/journey.json"> <h2 slot="title">Platform Evaluation</h2></user-journey>
| Variable | Default | Description |
|---|---|---|
--user-journey-bg |
#f8f9fa / #121212 |
Background for grid row labels and curve area |
--user-journey-card |
#ffffff / #1e1e1e |
Main card background |
--user-journey-border |
#e0e0e0 / #333333 |
Border color for card, grid lines, and curve vertical guides |
--user-journey-muted |
#666666 / #888888 |
Muted text color for labels, summary, and empty cells |
--user-journey-text |
#1a1a1a / #e8e8e8 |
Primary text color |
--user-journey-curve-stroke |
#6366f1 |
Stroke color for the emotion curve line |
--user-journey-radius |
12px |
Outer card border radius |
| Event | Detail | When |
|---|---|---|
journey-ready |
{ title, persona, phaseCount } |
Fires after the component finishes rendering (including after JSON load) |
aria-hidden="true" — it is decorative and the phase data is fully available in the grid table below<table> with aria-label describing the journey title and breakdown<th>) provide screen reader context for every data cellposition: sticky so they remain visible when the grid scrolls horizontallycontainer-type: inline-size for responsive adjustments without media queries<user-persona> — Persona cards linked via persona-id<user-story> — Story cards linked via story-ids<empathy-map> — Empathy maps using the same emotion vocabulary<impact-effort> — Prioritization matrix for triaging stories<story-map> — Story mapping linked via data-journey-phase