Stream Card Interaction States — Design Spec
Branch: design/stream-card-interaction-states
Date: 2026-06-01
Status: Implemented & verified (build ✓, 115 tests ✓)
This spec covers all interaction states for:
Component
File
StreamCard
src/pages/Streams.tsx + src/pages/Streams.css
StreamRow
src/components/treasuryOverviewPage/StreamRow.tsx + StreamRow.css
StreamsTable
src/components/treasuryOverviewPage/StreamsTable.tsx
Design tokens
src/design-tokens.css (section 9)
2.1 StreamCard (card list view)
State
Trigger
Visual change
Default
No interaction
--stream-card-bg surface, --stream-card-border subtle border
Hover
Mouse over card
Background lifts to --stream-card-hover-bg, border brightens to --stream-card-hover-border, translateY(-1px) lift, accent shadow
Focus-visible
Tab / keyboard focus
2px solid focus ring (--interactive-focus-ring) + 2px transparent offset via box-shadow
Selected
Click or Enter/Space on card
Accent border at full opacity, --stream-card-selected-bg tint, double-ring shadow
Selected + Hover
Hover while selected
Selected border preserved, shadow deepens
Selected + Focus
Focus while selected
Focus ring layered on top of selected shadow
Expanded
"Expand deep dive" button
--stream-card-expanded-bg tint, --stream-card-expanded-border, header area gets --stream-card-expanded-header-bg
Expanded + Selected
Both active simultaneously
Both class modifiers applied; expanded tint takes precedence for bg, selected border wins
2.2 StreamRow (table view)
State
Trigger
Visual change
Default
No interaction
--stream-row-bg, bottom border
Hover
Mouse over row
--stream-row-hover-bg
Focus-visible
Tab / arrow key focus
Inset 2px focus ring (--stream-row-focus-ring)
Selected
Click or Enter/Space
--stream-row-selected-bg tint + 3px left accent bar (inset 3px 0 0 var(--color-accent-primary))
Selected + Hover
Hover while selected
Color-mixed bg (80% selected + 20% hover)
Selected + Focus
Focus while selected
Left bar + inset focus ring both visible
3. Design Token Reference
All tokens live in src/design-tokens.css section 9. Dark-theme overrides are co-located.
/* Stream Card */
--stream-card-bg
--stream-card-border
--stream-card-hover-bg
--stream-card-hover-border
--stream-card-hover-shadow
--stream-card-hover-transform /* translateY(-1px) */
--stream-card-selected-bg
--stream-card-selected-border /* var(--color-accent-primary) */
--stream-card-selected-shadow
--stream-card-expanded-bg
--stream-card-expanded-border
--stream-card-expanded-header-bg
--stream-card-focus-ring /* 2px offset double-ring */
/* Stream Row */
--stream-row-bg
--stream-row-border
--stream-row-hover-bg
--stream-row-selected-bg
--stream-row-selected-border-left /* 3px solid accent */
--stream-row-focus-ring /* inset 2px */
/* Expand/Collapse animation */
--stream-expand-duration /* 200ms */
--stream-expand-easing /* cubic-bezier(0.4, 0, 0.2, 1) */
--stream-expand-transition
Property
Value
Rationale
Duration
200ms
Fast enough to feel snappy; slow enough to track visually
Easing
cubic-bezier(0.4, 0, 0.2, 1)
Material "standard" easing — decelerates into open, accelerates out of close
Properties animated
max-height, opacity
max-height from 0 → 2000px (large cap); opacity 0 → 1
Implementation
`data-state="open
closed"on.stream-card__expanded-wrapper`
Property
Value
Transform
translateY(-1px)
Duration
var(--transition-fast) = 150ms
Easing
ease-in-out
When prefers-reduced-motion: reduce is active:
All transitions are disabled (transition: none)
transform is forced to none
Expanded wrapper shows/hides instantly (no max-height animation)
5. Accessibility Annotations
5.1 WCAG 2.1 AA Contrast Audit
StreamCard (dark theme — most constrained)
Element
Foreground
Background
Ratio
Requirement
Pass
Card text (--text-vivid #e8ecf4)
#e8ecf4
Default bg #121a2a
12.5:1
4.5:1
✓
Card text on hover bg #151e2e
#e8ecf4
#151e2e
11.8:1
4.5:1
✓
Card text on selected bg
#e8ecf4
rgba(0,184,212,0.10) ≈ #132028
10.2:1
4.5:1
✓
Selected border #00b8d4 vs bg
#00b8d4
#121a2a
4.1:1
3:1 (UI)
✓
Focus ring #00d4aa vs bg
#00d4aa
#121a2a
5.2:1
3:1 (UI)
✓
Muted text #6b7a94 on bg
#6b7a94
#121a2a
4.6:1
4.5:1
✓
Element
Foreground
Background
Ratio
Requirement
Pass
Row text on default bg
#e8ecf4
#121a2a
12.5:1
4.5:1
✓
Row text on hover bg
#e8ecf4
#151e2e
11.8:1
4.5:1
✓
Row text on selected bg
#e8ecf4
rgba(0,184,212,0.12) ≈ #132229
10.2:1
4.5:1
✓
Left accent bar #00b8d4 vs bg
#00b8d4
#121a2a
4.1:1
3:1 (UI)
✓
Inset focus ring #00d4aa vs bg
#00d4aa
#121a2a
5.2:1
3:1 (UI)
✓
StreamCard list (src/pages/Streams.tsx)
Key
Behavior
Tab
Moves focus to next card
Shift+Tab
Moves focus to previous card
Enter or Space
Toggles selected state on focused card
Buttons inside card
Receive focus independently; stopPropagation prevents card selection
StreamsTable (src/components/treasuryOverviewPage/StreamsTable.tsx)
Key
Behavior
Tab
Enters table; focuses first row
ArrowDown
Moves focus to next row
ArrowUp
Moves focus to previous row
Home
Jumps to first row
End
Jumps to last row
Enter or Space
Toggles selected state on focused row
Tab (inside table)
Exits table
< article
role ="article "
tabindex ="0 "
aria-selected ="true|false "
aria-expanded ="true|false "
aria-label ="Dev Grant - Alice — Active "
>
< button aria-expanded ="true|false " aria-controls ="stream-expanded-STR-001 ">
Expand deep dive
</ button >
< div
id ="stream-expanded-STR-001 "
role ="region "
aria-label ="Dev Grant - Alice deep dive "
data-state ="open|closed "
>
<!-- expanded content -->
</ div >
</ article >
< table role ="grid " aria-label ="Active streams " aria-rowcount ="N ">
< thead >
< tr >
< th scope ="col "> STREAM</ th >
<!-- ... -->
</ tr >
</ thead >
< tbody >
< tr
role ="row "
tabindex ="0 "
aria-selected ="true|false "
>
<!-- cells -->
</ tr >
</ tbody >
</ table >
Page landmark (<main>)
Hero section buttons (Create stream, Open featured deep dive)
Filter/search controls
Stream cards (in DOM order) — each card is a single tab stop
Buttons inside expanded card (tab into them after card is focused)
Pagination controls
5.5 Screen Reader Announcements
Card selection: aria-selected change is announced by screen readers automatically
Expand/collapse: aria-expanded on the toggle button announces state change
Expanded region: role="region" with aria-label gives the expanded content a landmark
Breakpoint
StreamCard
StreamRow
≥1024px
Full layout, hover lift active
Full table, all columns
768px–1023px
Expanded layout collapses to single column
Table scrolls horizontally
375px–767px
Cards stack, padding reduced to 1rem
Table scrolls, action column visible
320px
Cards full-width, no horizontal overflow
Table scrolls, min-width preserved
Touch devices: hover states are suppressed by the browser's hover media query behavior. Selected and focus states remain fully functional via tap.
Case
Handling
Long Stellar address (56 chars)
TruncatedAddress component handles truncation with copy affordance
Zero-accrual stream
ZeroAccrualBanner shown above list; stream cards still selectable
Empty search results
.streams-empty-search shown; no cards rendered, no keyboard trap
Error state (future)
Card should receive aria-invalid="true" and a visible error indicator
Loading state
StreamsLoading skeleton shown; no interactive cards rendered
Single stream
Selection/deselection works; no arrow-key issues in table (boundary guards)
All streams filtered out
Empty state message; focus returns to search input
File
Change
src/design-tokens.css
Added section 9: stream card interaction state tokens (light + dark)
src/pages/Streams.css
Added interaction state CSS: hover, focus-visible, selected, expanded, animated wrapper
src/pages/Streams.tsx
StreamCard: added selected prop, is-selected/is-expanded classes, tabIndex, aria-selected, aria-expanded, aria-label, keyboard handler; Streams: added selectedStreamId state
src/components/treasuryOverviewPage/StreamRow.tsx
Replaced inline hover styles with CSS classes; added tabIndex, aria-selected, role="row", keyboard handler
src/components/treasuryOverviewPage/StreamRow.css
New file: all interaction state CSS for table rows
src/components/treasuryOverviewPage/StreamsTable.tsx
Added role="grid", aria-label, aria-rowcount, scope="col" on headers, selectedId state, arrow-key navigation handler
Contrast (run with browser DevTools or axe)
Keyboard-only walkthrough
Screen reader (NVDA/VoiceOver)
design: stream card hover, selected, expanded, and focus-ring states
- Add section 9 stream interaction tokens to design-tokens.css
(hover bg/border/shadow, selected border/shadow, expanded bg,
focus ring, expand animation duration/easing — light + dark)
- Streams.css: hover lift, focus-visible ring, selected accent border,
expanded header tint, animated expand/collapse wrapper
- Streams.tsx: StreamCard gains tabIndex, aria-selected, aria-expanded,
aria-label, keyboard handler (Enter/Space), is-selected/is-expanded
class modifiers; Streams page tracks selectedStreamId state
- StreamRow.tsx: replace inline hover styles with CSS classes;
add tabIndex=0, aria-selected, role=row, keyboard handler
- StreamRow.css: new file — all row interaction states with
WCAG 2.1 AA contrast-verified colors
- StreamsTable.tsx: role=grid, aria-label, aria-rowcount, scope=col
on headers, selectedId state, arrow-key / Home / End navigation
WCAG 2.1 AA: all text ≥4.5:1, UI elements ≥3:1, keyboard navigable,
prefers-reduced-motion respected.