Controls
ScrollBar
ScrollBar is a range control that displays a viewport moving through a larger extent. It supports horizontal and vertical orientations, standalone interaction, and template-based visuals built around a Track, a draggable Thumb, and line-step buttons.
Quick start
Use ScrollBar when you want to expose scroll state, viewport position, or range navigation on a custom surface. For standard content scrolling, use ScrollViewer, which creates and synchronizes its own internal scroll bars automatically.
Minimal horizontal bar
<ScrollBar Width="280"
Orientation="Horizontal"
Minimum="0"
Maximum="100"
ViewportSize="20"
Value="35" />
Vertical bar for a tall surface
<ScrollBar Height="220"
Orientation="Vertical"
Minimum="0"
Maximum="1200"
ViewportSize="240"
Value="360" />
Styled bar using control colors
<ScrollBar Width="300"
Orientation="Horizontal"
Minimum="0"
Maximum="800"
ViewportSize="160"
Value="240"
Background="#152231"
Foreground="#6FB7FF"
BorderBrush="#27425A"
BorderThickness="1" />
Rule of thumb: use a standalone ScrollBar when your code owns the range and offset values. Use ScrollViewer when the bar is simply the scrolling chrome for UI content.
What the range means
ScrollBar models a visible window inside a larger logical extent. The key point is that Maximum represents the end of the total extent, not the last legal scroll offset.
Extent
The total logical length is Maximum - Minimum. This is the full content size or domain size the bar represents.
Viewport
ViewportSize is the visible window inside that extent. The thumb size is derived from it.
Offset
Value is the current viewport origin. For a typical scroller, the legal offset range is Minimum through Minimum + max(0, Maximum - Minimum - ViewportSize).
Clamping
Value is coerced into the legal scrollable range, so direct assignment, thumb drag, and page/line interaction all clamp consistently.
<ScrollBar Width="320"
Orientation="Horizontal"
Minimum="0"
Maximum="1000"
ViewportSize="320"
Value="180" />
In that example, the represented extent is 1000 units, the visible window is 320 units, and the largest practical offset is 680, not 1000.
Thumb sizing and movement
The thumb is laid out inside the Track area between the two line buttons. Its size and position are driven by the extent, viewport, and current value.
When the thumb fills the track
If Maximum - Minimum is effectively zero, the thumb fills the available track area. This is how the control represents a non-scrollable extent.
Viewport-based ratio
When ViewportSize is positive, thumb length is based on ViewportSize / extent, clamped between 5% and 100% of the track area.
Minimum thumb size
The rendered thumb never shrinks below 14 layout units, even when the represented extent is much larger than the viewport.
- If
ViewportSizeis0, the thumb falls back to roughly10%of the track, still respecting the14-unit minimum. - The thumb position is normalized across the scrollable range, not the full extent.
- The line buttons are arranged first, then the remaining track space is used for the thumb and page regions.
<StackPanel>
<ScrollBar Width="260"
Orientation="Horizontal"
Minimum="0"
Maximum="100"
ViewportSize="80"
Value="10" />
<ScrollBar Width="260"
Orientation="Horizontal"
Minimum="0"
Maximum="100"
ViewportSize="10"
Value="10"
Margin="0,8,0,0" />
</StackPanel>
The first bar shows a large thumb because the viewport covers most of the extent. The second shows a much smaller thumb because the viewport is only a small slice of the same range.
Template structure
ScrollBar is a templated control, not a content host. The default template uses named parts, and custom templates must preserve those parts for the control to function.
Required parts
PART_Track, PART_Thumb, PART_LineUpButton, and PART_LineDownButton are all required.
Part ownership
The thumb and both line buttons must live inside PART_Track, because the track owns thumb layout and page-hit regions.
- The default template root is a
Track. - The line buttons are
RepeatButtoninstances, so holding the pointer continues line stepping. - The thumb is a
Thumb, which supplies drag events and pointer capture for thumb movement.
Template example
<ControlTemplate TargetType="ScrollBar">
<Track x:Name="PART_Track">
<RepeatButton x:Name="PART_LineUpButton" />
<Thumb x:Name="PART_Thumb" />
<RepeatButton x:Name="PART_LineDownButton" />
</Track>
</ControlTemplate>
Interaction model
The control owns its own pointer behavior. The same interaction rules apply whether the bar is used on its own or inside a ScrollViewer.
Thumb drag
Pressing the thumb starts a real drag through Thumb.DragStarted, DragDelta, and DragCompleted. The thumb captures the pointer while dragging.
Track click
Clicking before or after the thumb moves the value by LargeChange toward the clicked side.
- The decrease and increase buttons move the value by
SmallChange. - Track clicks use
LargeChange. - Drag math and track clicks clamp the resulting value to the legal scrollable range.
ValueChangedis raised whenever the effective value changes.
Interaction example
var bar = new ScrollBar
{
Orientation = Orientation.Vertical,
Height = 240f,
Minimum = 0f,
Maximum = 1200f,
ViewportSize = 240f,
SmallChange = 24f,
LargeChange = 240f
};
bar.ValueChanged += (_, _) =>
{
cameraY = bar.Value;
};
Layout and sizing
ScrollBar is not a content host. Its size comes from normal layout and from whatever template you apply, so standalone bars are easiest to control when you set an explicit Width or Height.
Vertical bars
Set an explicit Height for predictable along-axis size. Set Width when you want a specific cross-axis thickness.
Horizontal bars
Set an explicit Width for predictable along-axis size. Set Height when you want a specific cross-axis thickness.
- Use normal layout properties like
WidthandHeightto size the control. - The default template uses square-ish line buttons sized from the available bar thickness.
- Custom templates can replace the visual structure, as long as the required parts remain present and wired inside
PART_Track.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Canvas Grid.Column="0" Width="420" Height="220" />
<ScrollBar Grid.Column="1"
Width="16"
Height="220"
Orientation="Vertical"
Minimum="0"
Maximum="600"
ViewportSize="220"
Value="120" />
</Grid>
Appearance and styling
The default template is visually conservative and intended to be restyled. The normal control styling surface drives the default visuals.
Default color bindings
Background feeds the default track fill. Foreground feeds the default thumb fill. BorderBrush and BorderThickness feed the default track border and thumb outline.
Template freedom
You can replace the template structure, button visuals, and thumb visuals. What must stay stable is the required part contract.
- The default line buttons use the bar's
Foregroundfor their arrow glyphs. - Template-driven styling works for standalone bars and for bars hosted inside
ScrollViewer. - The default template binds the track border and thumb outline to the control's border styling surface.
<StackPanel>
<ScrollBar Width="280"
Orientation="Horizontal"
Minimum="0"
Maximum="300"
ViewportSize="80"
Value="60"
Background="#132131"
Foreground="#77D1FF"
BorderBrush="#365B7D"
BorderThickness="1" />
<ScrollBar Width="280"
Orientation="Horizontal"
Minimum="0"
Maximum="300"
ViewportSize="80"
Value="60"
Background="#1B2D41"
Foreground="#FFB347"
BorderBrush="#6D5334"
BorderThickness="1"
Margin="0,8,0,0" />
</StackPanel>
The first bar keeps a cool track and thumb palette. The second uses a warmer track/thumb combination without needing any dedicated scrollbar-only color properties.
ScrollViewer ownership
ScrollViewer creates and owns its internal horizontal and vertical ScrollBar instances. Those bars are regular ScrollBar controls whose range values are synchronized by the viewer.
Values supplied by ScrollViewer
The viewer sets Minimum to 0, Maximum to the content extent, ViewportSize to the current viewport size, Value to the current scroll offset, and the change steps to the viewer's line and page sizes.
Thickness resolution
ScrollViewer.ScrollBarThickness controls the reserved layout space by default. Explicit Width or Height on an internal bar can still change the bar's actual slot.
<ScrollViewer Width="360"
Height="220"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
ScrollBarThickness="16">
<Canvas Width="960" Height="640">
<Border Canvas.Left="40"
Canvas.Top="32"
Width="200"
Height="120"
Background="#183149"
BorderBrush="#5A9CD4"
BorderThickness="1" />
</Canvas>
</ScrollViewer>
If your goal is end-user scrolling of a content surface, this is the usual pattern. The viewer owns the offsets, extents, and viewport metrics, while the internal bars surface those values through the normal scrollbar UI.
Property and event reference
Range properties
Orientation: Vertical or Horizontal. Default: Vertical.
Minimum: lower bound of the represented extent. Default: 0.
Maximum: upper bound of the represented extent. Default: 0.
Value: current viewport origin or logical offset. Default: 0.
ViewportSize: visible window size inside the extent. Default: 0.
Interaction and rendering
SmallChange: line-step amount used by the repeat buttons. Default: 16.
LargeChange: page step used for track clicks. Default: 32.
ValueChanged: routed event raised when the effective value changes.
Template: custom control template. Required parts must be present for the control to function.
Styling surface
Background: default template track fill. Default: (42, 42, 42).
Foreground: default template thumb fill and arrow foreground. Default: (112, 112, 112).
BorderBrush: default template border and thumb outline color. Default: transparent.
BorderThickness: default template track border thickness. Default: empty.
Automation
ScrollBar participates in the framework automation surface as a scroll-bar control type and exposes range values through the automation peer.
Patterns and examples
Viewport indicator for a large canvas
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Canvas x:Name="WorldCanvas" Grid.Column="0" Width="1200" Height="900" />
<ScrollBar Grid.Column="1"
Height="260"
Orientation="Vertical"
Minimum="0"
Maximum="900"
ViewportSize="260"
Value="{Binding CameraY}"
Background="#13202E"
Foreground="#5FA8E8"
BorderBrush="#27445A"
BorderThickness="1" />
</Grid>
Horizontal timeline navigator
<ScrollBar Width="420"
Orientation="Horizontal"
Minimum="0"
Maximum="{Binding TimelineLength}"
ViewportSize="{Binding VisibleRange}"
Value="{Binding TimelineOffset}"
Background="#0F1823"
Foreground="#F5B04C" />
Programmatic setup in code
var bar = new ScrollBar
{
Orientation = Orientation.Vertical,
Width = 16f,
Height = 240f,
Minimum = 0f,
Maximum = documentHeight,
ViewportSize = viewportHeight,
Value = currentOffset,
Background = new Color(19, 32, 46),
Foreground = new Color(95, 168, 232),
BorderBrush = new Color(39, 68, 90),
BorderThickness = new Thickness(1f)
};
bar.Value = nextOffset;
bar.ValueChanged += (_, _) => camera.Y = bar.Value;
Custom template styling
<Style TargetType="ScrollBar">
<Setter Property="Background" Value="#102030" />
<Setter Property="Foreground" Value="#76C7FF" />
<Setter Property="BorderBrush" Value="#28455C" />
<Setter Property="BorderThickness" Value="1" />
</Style>
Prefer ScrollViewer for interactive content scrolling
<ScrollViewer Width="340"
Height="220"
VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Text="Entry 1" />
<TextBlock Text="Entry 2" />
<TextBlock Text="Entry 3" />
<TextBlock Text="Entry 4" />
<TextBlock Text="Entry 5" />
<TextBlock Text="Entry 6" />
<TextBlock Text="Entry 7" />
</StackPanel>
</ScrollViewer>
Notes and pitfalls
Maximum is not the largest offset. The effective end of the scrollable range is Maximum - ViewportSize relative to Minimum, clamped at zero.
ScrollBar does not host content. It is a range control, not a ContentControl. Put real UI content in a ScrollViewer or another container, not inside the bar itself.
Custom templates must keep the required part contract. If PART_Track, PART_Thumb, PART_LineUpButton, or PART_LineDownButton is missing, or if the thumb/buttons are not inside the track, the control will throw when the template is applied.
Background, Foreground, and BorderBrush are colors, not brush objects. Examples and styles for this control should use values that resolve to Color.
Set an explicit length when using the control on its own. A standalone bar is easiest to reason about when you set the scrolling-axis length with Width or Height.