Scroll Snap
Swipe/drag paged scroll container that snaps to discrete pages by index.
Summary
ScrollSnap is a page-based UI Toolkit container that arranges children as pages and snaps to a page boundary after gesture or wheel input. It supports:
- Horizontal or vertical paging
- Programmatic page navigation (
GoToPage,MoveNext,MovePrevious) - Optional touch/pointer gesture control
- Optional validation/restriction flow for swipe attempts
- Smooth snap animation with configurable easing
- Page spacing via per-page paddings
Typical use cases:
- Onboarding or wizard flows
- Carousel-like page navigation
- Validation-gated step transitions (for example: required fields before moving forward)
Properties
| Name | Description | Options |
|---|---|---|
Orientation |
Paging axis for layout and swipe direction. | Horizontal (default), Vertical |
PageSize |
Explicit page size in pixels. If <= 0, uses viewport resolved size (width for horizontal, height for vertical). |
float (<= 0 means auto) |
ManualMovementEnabled |
Enables pointer/touch and wheel gesture handling by the control. If disabled, input gestures are ignored and only programmatic navigation changes pages. | true / false (default false) |
OnlySinglePageSwipeAllowed |
Limits swipe transitions to one page max per completed gesture. Keeps behavior predictable for step-based flows. | true / false (default true) |
ValidatePageChange |
Enables swipe validation behavior using direction flags and optional async validator callback. | true / false (default false) |
CanMoveNextPage |
Forward movement gate when validation is enabled. Typically set externally before allowing the next forward step. | true / false (default true, reset to false after page change when validation is enabled) |
CanMoveBackPage |
Backward movement gate when validation is enabled. | true / false (default true, reset to false after page change when validation is enabled) |
AllowMoveBack |
Master backward permission gate when validation is enabled. If false, backward swipe attempts are restricted and snap back. |
true / false (default true) |
ValidationDragLimit |
Maximum blocked-drag preview distance as a fraction of page size (0..1). Example: 0.2 allows 20% drag before clamp while restricted. |
float in 0..1 (default 0.2) |
IsValidatingPageChange |
Read-only state indicating an async page validation callback is currently running. | bool (read-only) |
PagePaddingLeft |
Left padding/gap per page (applied as margin). | float pixels, >= 0 |
PagePaddingRight |
Right padding/gap per page (applied as margin). | float pixels, >= 0 |
PagePaddingTop |
Top padding/gap per page (applied as margin). | float pixels, >= 0 |
PagePaddingBottom |
Bottom padding/gap per page (applied as margin). | float pixels, >= 0 |
PageCount |
Number of child pages in contentContainer. |
int (read-only) |
CurrentPageIndex |
Current snapped page index. | int (read-only) |
USS Custom Properties
| Name | Description | Default |
|---|---|---|
--scrollsnap-easing |
Snap animation easing function name. | Linear |
--scrollsnap-page-padding-left |
Left page margin/padding from USS. | 0px |
--scrollsnap-page-padding-right |
Right page margin/padding from USS. | 0px |
--scrollsnap-page-padding-top |
Top page margin/padding from USS. | 0px |
--scrollsnap-page-padding-bottom |
Bottom page margin/padding from USS. | 0px |
--scrollsnap-validation-drag-limit |
Optional validation drag-limit value (fraction). Use values like 0.2, 0.15, 0.3. |
Not set unless provided |
Events
| Name | Description | Arguments |
|---|---|---|
PageChanged |
Fired when CurrentPageIndex changes after a completed transition/snap. |
(int currentPageIndex) |
OnPageStartChange |
Fired when a swipe attempt resolves to a target page and validation logic begins. Host can pre-load target content and inspect whether movement is currently allowed by flags. | (int targetPage, bool moveAllowed) |
OnPageChangeRestricted |
Fired when a swipe attempt is denied and the control has completed returning/snapping back to the active page. Useful for user feedback animations/toasts. The argument is the attempted destination page index. | (int attemptedTargetPage) |
OnValidatePageTransition |
Optional async validator callback. Called when validation mode is enabled and direction flags allow movement. Return true to proceed, false to restrict and snap back. |
(int targetPage) => Task<bool> |
Note: OnPageChangePrevented is not a built-in ScrollSnap event name. It is a consumer-defined callback method name commonly subscribed to OnPageChangeRestricted, for example: scrollSnap.OnPageChangeRestricted += OnPageChangePrevented;.
Using the Control in Manual Mode
Manual mode means gesture input is disabled and page movement is API-driven.
- Set
ManualMovementEnabled = false - Navigate only through
GoToPage,MoveNext,MovePrevious - Use
force: truewhen you want to bypass validation gates
Example:
var snap = new ScrollSnap();
snap.ManualMovementEnabled = false;
// Programmatic navigation
snap.GoToPage(2, animate: true);
snap.MoveNext(animate: true);
// Always move, even if validation mode and flags are restrictive
snap.MoveNext(animate: true, force: true);
Recommended when:
- Navigation is fully controlled by external UI buttons
- You need deterministic transitions with no user drag gestures
- Validation/permissions are handled outside swipe interaction
Using the Control in Swipe Mode
Swipe mode means gesture input is enabled and users can drag/swipe pages directly.
- Set
ManualMovementEnabled = true - Optionally keep
OnlySinglePageSwipeAllowed = truefor step-by-step flows - Enable
ValidatePageChange = trueto gate movement with restriction behavior
Example:
var snap = new ScrollSnap();
snap.ManualMovementEnabled = true;
snap.OnlySinglePageSwipeAllowed = true;
snap.ValidatePageChange = true;
// Host-controlled movement gates
snap.CanMoveNextPage = false;
snap.CanMoveBackPage = true;
snap.AllowMoveBack = true;
snap.OnPageStartChange += (targetPage, moveAllowed) =>
{
// Prepare content for target page, optionally show pre-transition UI
};
snap.OnPageChangeRestricted += targetPage =>
{
// Show why movement is blocked
};
snap.OnValidatePageTransition = async targetPage =>
{
// Async validation (API call, form checks, etc.)
await Task.Yield();
return true;
};
Restricted Movement and Event Behavior
When ValidatePageChange = true, swipe transitions follow this flow:
- User swipes and a target page is derived.
- Direction gates are checked:
- Forward:
CanMoveNextPage - Backward:
AllowMoveBackandCanMoveBackPage
- Forward:
OnPageStartChange(targetPage, moveAllowed)is fired.- If blocked by gates:
- Drag is clamped by
ValidationDragLimit - On release, the control snaps back
OnPageChangeRestricted(targetPage)is fired after snap-back completes
- Drag is clamped by
- If allowed by gates and
OnValidatePageTransitionis assigned:- Async callback runs
true=> transition proceedsfalse=> restricted snap-back andOnPageChangeRestricted(after snap-back completes)
- On successful transition completion,
PageChanged(newPageIndex)is fired.
Authorization Flow (Validation-Gated Navigation)
Use this flow when page movement must be authorized by host logic (for example onboarding step completion, server-side checks, required field validation).
Core Idea
OnPageStartChangeis the early signal: user attempted a transition.moveAllowedindicates whether current direction gates allow a possible transition.OnValidatePageTransitionis the async authorization hook.OnPageChangeRestrictedis the post-snap-back signal (fires after return to the original page is complete).
Direction Detection
At OnPageStartChange(targetPage, moveAllowed), compare:
CurrentPageIndex(current/origin page)targetPage(attempted destination)
Rules:
targetPage > CurrentPageIndex=> forwardtargetPage < CurrentPageIndex=> backwardtargetPage == CurrentPageIndex=> no effective move
CurrentPageIndex does not advance until a successful page transition completes and PageChanged is raised.
Event Sequence: Authorized Success
OnPageStartChange(targetPage, true)- Optional
OnValidatePageTransition(targetPage)returnstrue - Transition animates to target
PageChanged(newPageIndex)
Event Sequence: Restricted by Gates
OnPageStartChange(targetPage, false)- Control animates back to origin page
OnPageChangeRestricted(attemptedTargetPage)fires after snap-back completion
Event Sequence: Async Authorization Denied
OnPageStartChange(targetPage, true)OnValidatePageTransition(targetPage)returnsfalse- Control animates back to origin page
OnPageChangeRestricted(attemptedTargetPage)fires after snap-back completion
Recommended Host Pattern
- In
OnPageStartChange: detect direction and optionally do lightweight target prep only whenmoveAllowedis true. - In
OnPageChangeRestricted: start validation/error animation (this now runs when the control has already returned to the origin page). - In
PageChanged: finalize UI state for the newly active page.
Important Notes
- While async validation is running,
IsValidatingPageChangeistrueand additional swipe gesture processing is ignored. CanMoveNextPageandCanMoveBackPageare reset tofalseafter a page change when validation mode is enabled.- Use fraction values for drag limit configuration (for example
0.2for 20%). - Programmatic movement remains available at all times via the
forceoverride:
snap.MoveNext(animate: true, force: true);
Programmatic API Summary
| Method | Purpose |
|---|---|
GoToPage(int index, bool animate = true, bool force = false) |
Navigate to a specific page |
MoveNext(bool animate = true, bool force = false) |
Navigate to next page |
MovePrevious(bool animate = true, bool force = false) |
Navigate to previous page |
Use force: true when a host flow has completed external validation and wants to advance immediately regardless of current swipe-gate state.