Skip to content
# SwipeTrap **Also known as:** Gesture, Swipeable ## Description SwipeTrap traps swipe gestures - touch, mouse or pen - on its content and reports them as start, move, end and trigger events carrying distance, velocity, duration and direction. It locks an axis, triggers by distance or by a flick, filters what it responds to, and lets the arrow keys trigger it as the keyboard's alternative. ## Parameters | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | ChildContent | `RenderFragment?` | null | The content of the swipe trap. | | KeyboardTrigger | `bool` | false | Lets the arrow keys raise OnTrigger in their own direction while the trap itself has the focus. Makes the trap a tab stop, names the keys in aria-keyshortcuts and honors a Horizontal or Vertical OrientationLock. The event carries zero distances and a PointerType of "keyboard". | | OnStart | `EventCallback<BitSwipeTrapEventArgs>` | | Raised when a swipe starts on the trap. | | OnMove | `EventCallback<BitSwipeTrapEventArgs>` | | Raised while a swipe moves, at most once per Throttle milliseconds. | | OnEnd | `EventCallback<BitSwipeTrapEventArgs>` | | Raised when a swipe is released, or canceled (IsCanceled) by the browser, by leaving the trap before it was trapped, or by Escape. | | OnTrigger | `EventCallback<BitSwipeTrapTriggerArgs>` | | Raised on the release of a swipe that passed Trigger or was flicked faster than TriggerVelocity, and on an arrow key with KeyboardTrigger. | | OrientationLock | `BitSwipeOrientation?` | null | Locks the trap to one axis. Horizontal and Vertical trap and report only that axis for the whole gesture, leaving the other to the browser (it reads zero); Auto locks to the axis the gesture moves along first. | | SkipSelector | `string?` | null | A CSS selector of descendants on which a swipe never starts, such as inputs or nested sliders. | | Threshold | `decimal?` | null | The distance in pixels a gesture covers before the trap takes it over; it also decides the axis of a diagonal one. Defaults to 0. | | Throttle | `int?` | null | The least time in milliseconds between two OnMove events; the latest move of a window still arrives when it closes. Defaults to 0 (no throttling). | | TouchOnly | `bool` | false | Ignores mouse swipes, trapping only touch and pen gestures. | | Trigger | `decimal?` | null | How far a swipe travels before its release triggers: a fraction of the trap's size per axis below 1, pixels from 1 up. Defaults to 0.25. | | TriggerVelocity | `decimal?` | null | The release velocity in px/ms that triggers a flick short of Trigger. Defaults to 0 (off). | | AriaLabel | `string?` | null | Gets or sets the accessible label for the component, used by assistive technologies. | | Class | `string?` | null | Gets or sets the CSS class name(s) to apply to the rendered element. | | Dir | `BitDir?` | null | Gets or sets the text directionality for the component's content. | | Disabled | `bool` | false | Gets or sets a value indicating whether the component is disabled and cannot respond to user interaction. | | ForceAnimation | `bool` | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. | | HtmlAttributes | `Dictionary<string, object>` | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. An attribute written on the component wins over an entry of the same name. | | Id | `string?` | null | Gets or sets the unique identifier for the component's root element. | | Style | `string?` | null | Gets or sets the CSS style string to apply to the rendered element. | | TabIndex | `string?` | null | Gets or sets the tab order index for the component when navigating with the keyboard. | | Visibility | `BitVisibility` | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. | ## Public Members | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | UniqueId | `Guid` | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. | | RootElement | `ElementReference` | | Gets the reference to the root HTML element associated with this component. | ## Enums ### BitSwipeOrientation Enum | Name | Value | Description | | :--- | :--- | :---------- | | None | 0 | No orientation lock for the swipe trap. | | Horizontal | 1 | Horizontal orientation lock of trapping the swipe action. | | Vertical | 2 | Vertical orientation lock of trapping the swipe action. | | Auto | 3 | Locks the trap to the first orientation the gesture moves along, trapping that axis and zeroing the other. | ### BitPlacement Enum | Name | Value | Description | | :--- | :--- | :---------- | | Top | 0 | The top edge. | | Bottom | 1 | The bottom edge. | | Start | 2 | The edge the reading direction starts from - the left in LTR, the right in RTL. On the vertical axis, which does not turn around, it is the top. | | End | 3 | The edge the reading direction ends at - the right in LTR, the left in RTL. On the vertical axis, which does not turn around, it is the bottom. | | Left | 4 | The left edge, in both reading directions. | | Right | 5 | The right edge, in both reading directions. | | Center | 6 | The middle of the axis, against neither edge. | | TopAndBottom | 7 | Both edges of the block axis at once. | | StartAndEnd | 8 | Both edges of the inline axis at once, following the reading direction the way Start and End do. | ### BitVisibility Enum | Name | Value | Description | | :--- | :--- | :---------- | | Visible | 0 | The content of the component is visible. | | Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). | | Collapsed | 2 | The component is hidden (display:none). | ### BitDir Enum | Name | Value | Description | | :--- | :--- | :---------- | | Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). | | Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). | | Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. | ## Sub Classes ### BitSwipeTrapEventArgs Properties The event arguments of the SwipeTrap events. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | StartX | `decimal` | 0 | The horizontal start point of the swipe action in pixels, relative to the viewport. | | StartY | `decimal` | 0 | The vertical start point of the swipe action in pixels, relative to the viewport. | | DiffX | `decimal` | 0 | The horizontal difference of swipe action in pixels. | | DiffY | `decimal` | 0 | The vertical difference of swipe action in pixels. | | VelocityX | `decimal` | 0 | The horizontal velocity of the swipe action in pixels per millisecond. | | VelocityY | `decimal` | 0 | The vertical velocity of the swipe action in pixels per millisecond. | | PointerType | `string?` | null | The type of the pointer that performed the swipe action: "mouse", "touch" or "pen". | | IsCanceled | `bool` | false | Whether the swipe was canceled (the browser took it over, it left the trap before being trapped, or Escape was pressed) rather than released. Only meaningful in OnEnd. | | Duration | `decimal` | 0 | The elapsed time of the swipe action in milliseconds, measured from the moment it started. | ### BitSwipeTrapTriggerArgs Properties The event arguments of the SwipeTrap trigger event. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Direction | `BitPlacement` | | The swipe direction in which the action triggered. It is always one of the physical four - Top, Bottom, Left or Right - read off the screen rather than off the reading direction. | | DiffX | `decimal` | 0 | The horizontal difference of swipe action in pixels. | | DiffY | `decimal` | 0 | The vertical difference of swipe action in pixels. | | VelocityX | `decimal` | 0 | The horizontal velocity of the swipe action in pixels per millisecond. | | VelocityY | `decimal` | 0 | The vertical velocity of the swipe action in pixels per millisecond. | | PointerType | `string?` | null | The type of the pointer that performed the swipe action: "mouse", "touch" or "pen" - or "keyboard" for an arrow key with KeyboardTrigger. | | Duration | `decimal` | 0 | The elapsed time of the swipe action in milliseconds, measured from the moment it started. | ## CSS Variables Public CSS custom properties read off the component's root. Set one on `:root` to restyle every instance, on any ancestor to restyle the ones inside it, or on the `Style` of one instance to restyle it and every instance nested inside it, which inherits the value too - a parameter changes one alone, as does the real CSS property on its `Style` / `Styles` when that property does not inherit (`opacity`); an inherited one (`color`, `font-family`) reaches the nested instances that do not set it themselves. | Name | Default Value | Description | | :--- | :------------ | :---------- | | `--bit-SwipeTrap-cursor` | inherit | Pointer cursor over the trap at rest, e.g. grab. | | `--bit-SwipeTrap-swiping-cursor` | grabbing | Pointer cursor while a swipe is being trapped. | | `--bit-SwipeTrap-focus-color` | --bit-clr-pri-focus | Focus ring color of a trap the keyboard can reach (KeyboardTrigger). While it is unset, the focus ring is the library's own --bit-shd-focus-ring, so replacing that token re-shapes it too. | ## Examples \n**Basic**: ```razor
StartX: @swipeTrapEventArgsBasic?.StartX
StartY: @swipeTrapEventArgsBasic?.StartY
DiffX: @swipeTrapEventArgsBasic?.DiffX
DiffY: @swipeTrapEventArgsBasic?.DiffY
VelocityX: @swipeTrapEventArgsBasic?.VelocityX.ToString("0.00")
VelocityY: @swipeTrapEventArgsBasic?.VelocityY.ToString("0.00")
Duration: @swipeTrapEventArgsBasic?.Duration.ToString("0") ms
PointerType: @swipeTrapEventArgsBasic?.PointerType
IsCanceled: @swipeTrapEventArgsBasic?.IsCanceled
---
Triggered? @isTriggeredBasic
Trigger direction: @swipeTrapTriggerArgsBasic?.Direction
Trigger diffX: @swipeTrapTriggerArgsBasic?.DiffX
Trigger diffY: @swipeTrapTriggerArgsBasic?.DiffY
``` ```csharp private bool isTriggeredBasic; private BitSwipeTrapEventArgs? swipeTrapEventArgsBasic; private BitSwipeTrapTriggerArgs? swipeTrapTriggerArgsBasic; private void HandleOnStartBasic(BitSwipeTrapEventArgs args) { swipeTrapEventArgsBasic = args; } private void HandleOnMoveBasic(BitSwipeTrapEventArgs args) { swipeTrapEventArgsBasic = args; } private void HandleOnEndBasic(BitSwipeTrapEventArgs args) { swipeTrapEventArgsBasic = args; } private void HandleOnTriggerBasic(BitSwipeTrapTriggerArgs args) { isTriggeredBasic = true; swipeTrapTriggerArgsBasic = args; _ = Task.Delay(3000).ContinueWith(async _ => { isTriggeredBasic = false; swipeTrapEventArgsBasic = null; swipeTrapTriggerArgsBasic = null; await InvokeAsync(StateHasChanged); }); } ``` \n**Trigger**: ```razor
Trigger="0.5m"
(half of the box)
Direction: @triggerArgsFractional?.Direction
DiffX: @triggerArgsFractional?.DiffX
DiffY: @triggerArgsFractional?.DiffY
Trigger="80m"
(80 pixels)
Direction: @triggerArgsAbsolute?.Direction
DiffX: @triggerArgsAbsolute?.DiffX
DiffY: @triggerArgsAbsolute?.DiffY
TriggerVelocity="0.5m"
(a flick)
Direction: @triggerArgsFlick?.Direction
VelocityX: @triggerArgsFlick?.VelocityX.ToString("0.00")
VelocityY: @triggerArgsFlick?.VelocityY.ToString("0.00")
``` ```csharp private BitSwipeTrapTriggerArgs? triggerArgsFractional; private BitSwipeTrapTriggerArgs? triggerArgsAbsolute; private BitSwipeTrapTriggerArgs? triggerArgsFlick; private void HandleOnTriggerFractional(BitSwipeTrapTriggerArgs args) { triggerArgsFractional = args; } private void HandleOnTriggerAbsolute(BitSwipeTrapTriggerArgs args) { triggerArgsAbsolute = args; } private void HandleOnTriggerFlick(BitSwipeTrapTriggerArgs args) { triggerArgsFlick = args; } ``` \n**OrientationLock**: ```razor
Horizontal
DiffX: @diffXHorizontalLock
DiffY: @diffYHorizontalLock
Vertical
DiffX: @diffXVerticalLock
DiffY: @diffYVerticalLock
Auto
DiffX: @diffXAutoLock
DiffY: @diffYAutoLock
``` ```csharp private decimal diffXHorizontalLock; private decimal diffYHorizontalLock; private decimal diffXVerticalLock; private decimal diffYVerticalLock; private decimal diffXAutoLock; private decimal diffYAutoLock; private void HandleOnMoveHorizontalLock(BitSwipeTrapEventArgs args) { diffXHorizontalLock = args.DiffX; diffYHorizontalLock = args.DiffY; } private void HandleOnEndHorizontalLock(BitSwipeTrapEventArgs args) { diffXHorizontalLock = 0; diffYHorizontalLock = 0; } private void HandleOnMoveVerticalLock(BitSwipeTrapEventArgs args) { diffXVerticalLock = args.DiffX; diffYVerticalLock = args.DiffY; } private void HandleOnEndVerticalLock(BitSwipeTrapEventArgs args) { diffXVerticalLock = 0; diffYVerticalLock = 0; } private void HandleOnMoveAutoLock(BitSwipeTrapEventArgs args) { diffXAutoLock = args.DiffX; diffYAutoLock = args.DiffY; } private void HandleOnEndAutoLock(BitSwipeTrapEventArgs args) { diffXAutoLock = 0; diffYAutoLock = 0; } ``` \n**Threshold & Throttle**: ```razor
Threshold="30"
(the first 30px are free)
DiffX: @diffXThreshold
DiffY: @diffYThreshold
Throttle="200"
(one move per 200ms at most)
Moves: @moveCountThrottle
DiffX: @diffXThrottle
DiffY: @diffYThrottle
``` ```csharp private decimal diffXThreshold; private decimal diffYThreshold; private int moveCountThrottle; private decimal diffXThrottle; private decimal diffYThrottle; private void HandleOnMoveThreshold(BitSwipeTrapEventArgs args) { diffXThreshold = args.DiffX; diffYThreshold = args.DiffY; } private void HandleOnEndThreshold(BitSwipeTrapEventArgs args) { diffXThreshold = 0; diffYThreshold = 0; } private void HandleOnMoveThrottle(BitSwipeTrapEventArgs args) { moveCountThrottle++; diffXThrottle = args.DiffX; diffYThrottle = args.DiffY; } private void HandleOnEndThrottle(BitSwipeTrapEventArgs args) { moveCountThrottle = 0; diffXThrottle = 0; diffYThrottle = 0; } ``` \n**Filtering**: ```razor
TouchOnly
(mouse drags are ignored)
DiffX: @diffXTouchOnly
DiffY: @diffYTouchOnly
SkipSelector
DiffX: @diffXSkip
DiffY: @diffYSkip
Disabled
(nothing is trapped)
Moves: @moveCountDisabled
``` ```csharp private decimal diffXTouchOnly; private decimal diffYTouchOnly; private decimal diffXSkip; private decimal diffYSkip; private int moveCountDisabled; private void HandleOnMoveTouchOnly(BitSwipeTrapEventArgs args) { diffXTouchOnly = args.DiffX; diffYTouchOnly = args.DiffY; } private void HandleOnEndTouchOnly(BitSwipeTrapEventArgs args) { diffXTouchOnly = 0; diffYTouchOnly = 0; } private void HandleOnMoveSkip(BitSwipeTrapEventArgs args) { diffXSkip = args.DiffX; diffYSkip = args.DiffY; } private void HandleOnEndSkip(BitSwipeTrapEventArgs args) { diffXSkip = 0; diffYSkip = 0; } private void HandleOnMoveDisabled(BitSwipeTrapEventArgs args) { moveCountDisabled++; } ``` \n**Keyboard**: ```razor
Left: snooze - Right: archive
Last action: @cardAction
PointerType: @cardPointerType
Last swipe: @cardLastEnd
``` ```csharp private string? cardAction; private string? cardPointerType; private string? cardLastEnd; private void HandleOnTriggerKeyboard(BitSwipeTrapTriggerArgs args) { cardAction = args.Direction == BitPlacement.Right ? "Archived" : "Snoozed"; cardPointerType = args.PointerType; } private void HandleOnEndKeyboard(BitSwipeTrapEventArgs args) { cardLastEnd = args.IsCanceled ? "canceled" : "released"; } ``` \n**List**: ```razor
@foreach (int idx in itemsList) { var i = idx;
Delete
Item@(i + 1)
}
Reset ``` ```csharp private int deletingIndex = -1; private bool isListDialogOpen; private TaskCompletionSource? listTcs; private List itemsList = Enumerable.Range(0, 10).ToList(); private decimal[] diffXList = Enumerable.Repeat(0m, 10).ToArray(); private void HandleOnMoveList(BitSwipeTrapEventArgs args, int index) { diffXList[index] = args.DiffX; } private void HandleOnEndList(BitSwipeTrapEventArgs args, int index) { if (diffXList[index] < 60) { diffXList[index] = 0; } } private async Task HandleOnTriggerList(BitSwipeTrapTriggerArgs args, int index) { if (args.Direction == BitPlacement.Right) { await ConfirmDeleteList(index); } } private async Task ConfirmDeleteList(int index) { deletingIndex = index; listTcs = new(); isListDialogOpen = true; await listTcs.Task; isListDialogOpen = false; diffXList[index] = 0; deletingIndex = -1; } private string GetRowStyle(int index) { var x = Math.Min(diffXList[index], 60); return x > 0 ? $"transform: translateX({x}px)" : ""; } private void HandleOnOkList() { if (deletingIndex != -1) { itemsList.Remove(deletingIndex); } listTcs?.SetResult(); } private void HandleOnCancelList() { listTcs?.SetResult(); } private void ResetList() { itemsList = Enumerable.Range(0, 10).ToList(); } ``` \n**Advanced**: ```razor
bit BlazorUI
Swipe left or right

Left Menu

Item1
Item2
Item3

Right Menu

Item1
Item2
Item3
``` ```csharp private decimal? diffXPanelAdvanced; private BitPlacement? direction; private BitPlacement? panelOpen; private void OpenPanelAdvanced(BitPlacement swipeDirection) { if (panelOpen == swipeDirection) return; direction = null; panelOpen = swipeDirection; diffXPanelAdvanced = 0; } private void ClosePanelAdvanced() { panelOpen = null; diffXPanelAdvanced = null; } private void TogglePanelAdvanced(BitPlacement side) { if (panelOpen == side) { ClosePanelAdvanced(); } else { OpenPanelAdvanced(side); } } private string IsPanelOpenAdvanced(BitPlacement side) => panelOpen == side ? "true" : "false"; private void HandleOnMovePanelAdvanced(BitSwipeTrapEventArgs args) { diffXPanelAdvanced = args.DiffX; if (Math.Abs(args.DiffX) > 2 || Math.Abs(args.DiffY) > 2) { direction = Math.Abs(args.DiffX) >= Math.Abs(args.DiffY) ? args.DiffX > 0 ? BitPlacement.Right : BitPlacement.Left : args.DiffY > 0 ? BitPlacement.Bottom : BitPlacement.Top; } else { direction = null; } } private void HandleOnEndPanelAdvanced(BitSwipeTrapEventArgs args) { if (panelOpen.HasValue) { diffXPanelAdvanced = 0; } else { diffXPanelAdvanced = null; } } private void HandleOnTriggerPanelAdvanced(BitSwipeTrapTriggerArgs args) { if (args.Direction == BitPlacement.Left) { if (panelOpen.HasValue is false || panelOpen == BitPlacement.Right) { OpenPanelAdvanced(BitPlacement.Right); } else if (panelOpen == BitPlacement.Left) { ClosePanelAdvanced(); } } else if (args.Direction == BitPlacement.Right) { if (panelOpen.HasValue is false || panelOpen == BitPlacement.Left) { OpenPanelAdvanced(BitPlacement.Left); } else if (panelOpen == BitPlacement.Right) { ClosePanelAdvanced(); } } } private string GetLeftPanelAdvancedStyle() { if (panelOpen == BitPlacement.Left && direction != BitPlacement.Left) { return "transform: translateX(0px)"; } else if ((panelOpen.HasValue is false && direction == BitPlacement.Right) || (panelOpen == BitPlacement.Left && direction == BitPlacement.Left)) { return diffXPanelAdvanced switch { 0 or > 200 => "transform: translateX(0px)", < 0 and < 200 => $"transform: translateX({diffXPanelAdvanced}px)", > 0 => $"transform: translateX(calc(-100% + {diffXPanelAdvanced}px))", _ => string.Empty }; } return string.Empty; } private string GetRightPanelAdvancedStyle() { if (panelOpen == BitPlacement.Right && direction != BitPlacement.Right) { return "transform: translateX(0px)"; } else if ((panelOpen.HasValue is false && direction == BitPlacement.Left) || (panelOpen == BitPlacement.Right && direction == BitPlacement.Right)) { return diffXPanelAdvanced switch { 0 or < -200 => "transform: translateX(0px)", > 0 => $"transform: translateX({diffXPanelAdvanced}px)", < 0 => $"transform: translateX(calc(100% - {(-1 * diffXPanelAdvanced)}px))", _ => string.Empty }; } return string.Empty; } ``` \n**Cascading parameters**: ```razor
From BitParams
(horizontal, 60px, arrow keys)
Direction: @triggerArgsCascaded?.Direction
Its own vertical lock
(the rest from BitParams)
Direction: @triggerArgsCascadedOwn?.Direction
``` ```csharp private BitSwipeTrapTriggerArgs? triggerArgsCascaded; private BitSwipeTrapTriggerArgs? triggerArgsCascadedOwn; private readonly BitSwipeTrapParams[] swipeTrapParams = [ new() { OrientationLock = BitSwipeOrientation.Horizontal, Trigger = 60m, KeyboardTrigger = true } ]; private void HandleOnTriggerCascaded(BitSwipeTrapTriggerArgs args) { triggerArgsCascaded = args; } private void HandleOnTriggerCascadedOwn(BitSwipeTrapTriggerArgs args) { triggerArgsCascadedOwn = args; } ``` \n**Style & Class**: ```razor
Style
Class (swipe me)
CSS variables (hover, swipe, Tab)
``` \n**RTL**: ```razor
راست: تعویق - چپ: بایگانی
آخرین اقدام: @rtlCardAction
``` ```csharp private string? rtlCardAction; private void HandleOnTriggerRtl(BitSwipeTrapTriggerArgs args) { // The directions are physical, so in a right-to-left layout the end of the line is on the left. rtlCardAction = args.Direction == BitPlacement.Left ? "بایگانی شد" : "به تعویق افتاد"; } ```