Documentation
$scroll / $drag
Create progress-driven animations tied to gestures and scroll.
Quick Start
The property:driver syntax ties any property to a progress source:
// Collapsing toolbar
<Image height:scroll={[320, 56]} />
// Bottom sheet
val sheet = $drag-y({ anchors: [0, 0.7, 1] })
<Box height:sheet={[64, 300, 64]} /> Built-in Drivers
| Driver | Use Case |
|---|---|
$scroll | Scroll-driven animations (collapsing toolbar, parallax) |
$drag-x | Horizontal drag (swipe actions, drawers) |
$drag-y | Vertical drag (bottom sheets, pull-to-refresh) |
$pinch | Pinch to zoom |
$rotate | Rotation gestures |
$tap | Tap to toggle (accordion, expandable cards) |
$hover | Hover effects (desktop) |
$press | Press state (button feedback) |
$pager | Pager position (tab indicator) |
Syntax Levels
Level 1: Built-in Preset
<Image height:scroll={[320, 56]} />
<Card height:tap={[80, 300]} /> Level 2: Built-in + Config
<Image height:scroll={[320, 56], { to: 250 }} />
<Sheet height:drag-y={[64, 500], { velocity: 500 }} /> Level 3: Named Driver
val sheet = $drag-y({ anchors: [0, 0.7, 1], velocity: 500 })
<Image height:sheet={[64, 300, 64]} />
<Text opacity:sheet={[1, 0.5, 0]} />
<Text fontSize:sheet={[14, 24, 14]} /> Level 4: Expression Form
val scroll = $scroll({ to: 200 })
val pinch = $pinch({ min: 1, max: 3 })
// Multiply two drivers
<Image scale={scroll([1, 1.3]) * pinch([1, 3])} /> Collapsing Toolbar Example
<LazyColumn>
<Image
src={show.poster}
height:scroll={[320, 56]}
fit="cover"
/>
<Text
fontSize:scroll={[32, 20]}
opacity:scroll={[1, 0]}
>
{show.name}
</Text>
@for (episode in episodes) {
<EpisodeCard episode={episode} />
}
</LazyColumn> Bottom Sheet Example
val sheet = $drag-y({ anchors: [0, 0.7, 1] })
<Box>
<!-- Main content -->
<Column>
<Text>Main screen content</Text>
</Column>
<!-- Bottom sheet -->
<Box
height:sheet={[64, 300, 64]}
background="#1a1a1a"
cornerRadius={16}
>
<Column p={16}>
<Image
width:sheet={[48, 200, 48]}
height:sheet={[48, 200, 48]}
src={song.artwork}
/>
<Text fontSize:sheet={[14, 24, 14]}>
{song.title}
</Text>
</Column>
</Box>
</Box> Driver Properties
Named drivers expose runtime properties:
val sheet = $drag-y({ anchors: [0, 0.7, 1] })
sheet.progress // 0.0 to 1.0
sheet.state // Current anchor index (0, 1, or 2)
sheet.snapTo(1) // Animate to anchor 1
sheet.jumpTo(0) // Instant jump to anchor 0
<Text>Sheet position: {sheet.progress}</Text> Morphable Properties
These properties can be driven by gestures/scroll:
- Size: width, height, size
- Position: x, y, offset
- Transform: scale, scaleX, scaleY, rotation
- Appearance: opacity, alpha
- Shape: cornerRadius, borderWidth
- Typography: fontSize, letterSpacing
- Color: backgroundColor, textColor
Cross-Component Sharing
Via Props
// Parent
val mainScroll = $scroll()
<LazyColumn>
<HeroImage scroll={mainScroll} />
</LazyColumn>
// HeroImage.wh
@prop scroll: Driver
<Image height:scroll={[320, 80]} /> Via Context
// Parent
val mainScroll = $scroll()
$context('mainScroll') = mainScroll
// Deeply nested child
val scroll = $context('mainScroll')
<Image height:scroll={[320, 80]} /> Interpolation Methods
val sheet = $drag-y({ anchors: [0, 0.7, 1] })
<Image
height:sheet={[48, 300, 48]} // linear (default)
opacity={sheet.step(0.5, 1, 0)} // step function
x={sheet.ease([8, 0, 8], "easeInOut")} // eased
scale={sheet.spring([1, 1.2, 1], { damping: 0.8 })} // spring
/> Expandable Card Example
<Card
height:tap={[80, 300]}
p={16}
onClick={() => {}} // Tap toggles
>
<Text>Card Title</Text>
<Text opacity:tap={[0, 1]}>
Expanded content here...
</Text>
</Card> Progress-driven morphing gives you fine-grained control over animations tied to user input. Perfect for bottom sheets, collapsing toolbars, and interactive UI.
See Also
- in: / out: / transition: - Screen transitions
- animate:flip - List animations
- transition:crossfade - Shared element transitions