Kebechet.Blazor.Swiper
14.0.6.2
Prefix Reserved
dotnet add package Kebechet.Blazor.Swiper --version 14.0.6.2
NuGet\Install-Package Kebechet.Blazor.Swiper -Version 14.0.6.2
<PackageReference Include="Kebechet.Blazor.Swiper" Version="14.0.6.2" />
<PackageVersion Include="Kebechet.Blazor.Swiper" Version="14.0.6.2" />
<PackageReference Include="Kebechet.Blazor.Swiper" />
paket add Kebechet.Blazor.Swiper --version 14.0.6.2
#r "nuget: Kebechet.Blazor.Swiper, 14.0.6.2"
#:package Kebechet.Blazor.Swiper@14.0.6.2
#addin nuget:?package=Kebechet.Blazor.Swiper&version=14.0.6.2
#tool nuget:?package=Kebechet.Blazor.Swiper&version=14.0.6.2
Blazor.Swiper
A Blazor wrapper for Swiper, built on the framework-agnostic Swiper Element web component. The Swiper bundle ships inside the package and is auto-registered on startup - no npm build step and no manual <script> tags.
Every Swiper parameter is typed, every Swiper event has a callback, and the callbacks are opt-in, so an event you don't wire up costs nothing.
Live storybook - an interactive story for every feature.
Installation
dotnet add package Kebechet.Blazor.Swiper
Usage
@using Kebechet.Blazor.Swiper
<Swiper Options="new() { Pagination = true, SpaceBetween = 16 }">
<SwiperSlide>Slide 1</SwiperSlide>
<SwiperSlide>Slide 2</SwiperSlide>
<SwiperSlide>Slide 3</SwiperSlide>
</Swiper>
Any attribute you put on <Swiper> or <SwiperSlide> that isn't a parameter is forwarded to the underlying <swiper-container> / <swiper-slide> element, so class, style and id work as usual. Styling the shadow-DOM controls is done through Swiper's CSS parts (::part(button-next)) or InjectStyles.
Two-way binding
@bind-ActiveIndex is the whole API for the common case. Setting it moves the slider; the slider moving sets it.
<Swiper @bind-ActiveIndex="_step">
@foreach (var page in _pages) { <SwiperSlide>@page</SwiperSlide> }
</Swiper>
<button @onclick="() => _step++">Next</button>
<p>Step @(_step + 1) of @_pages.Count</p>
@code {
private int _step;
}
- The value is always the index into your collection. In
Loopmode Swiper's own index counts duplicated slides that your collection doesn't have; a bound move routes through Swiper's loop-aware navigation so both directions agree. - A bound change animates at
Options.Speed. CallSlideTo(i, 0)for an instant jump. - A value supplied before the slider initializes becomes the slide it opens on, so binding an index of 2 starts on the third slide rather than starting at 0 and jumping.
@bind-IsAutoplayRunning works the same way, and is what a play/pause button wants - autoplay also stops itself, on interaction and at the last slide, and the binding follows that.
<Swiper @bind-IsAutoplayRunning="_playing" Options="new() { Autoplay = new() { Delay = 3000 } }">...</Swiper>
<button @onclick="() => _playing = !_playing">@(_playing ? "Pause" : "Play")</button>
Options
SwiperOptions mirrors Swiper's parameters one for one: every member is the PascalCase of Swiper's own name, camel-cased back by the interop serializer, so Swiper's reference reads directly onto this type.
Every member is nullable and unset by default. An unset member is dropped before the options reach Swiper, so Swiper's defaults are the only defaults and this type never restates them. false, 0 and "" are legitimate Swiper settings and do survive - only null is absence.
It's a record, so a change is a with expression, and changing it pushes the members that moved to the live slider (see Reactivity).
Layout and sizing
| Option | Type | Swiper's default |
|---|---|---|
Direction |
SwiperDirection? |
Horizontal |
InitialSlide |
int? |
0 |
Speed |
int? |
300 |
Width / Height |
double? |
null - forces a size, for initializing while hidden or prerendering |
AutoHeight |
bool? |
false |
RoundLengths |
bool? |
false |
SetWrapperSize |
bool? |
false |
VirtualTranslate |
bool? |
false |
Nested |
bool? |
false |
Effect |
SwiperEffect? |
Slide |
SpaceBetween |
SwiperLength? |
0 |
SlidesPerView |
SwiperSlidesPerView? |
1 |
SlidesPerGroup |
int? |
1 |
SlidesPerGroupSkip |
int? |
0 |
SlidesPerGroupAuto |
bool? |
false |
CenteredSlides |
bool? |
false |
CenteredSlidesBounds |
bool? |
false |
SlidesOffsetBefore / SlidesOffsetAfter |
double? |
0 |
NormalizeSlideIndex |
bool? |
true |
CenterInsufficientSlides |
bool? |
false |
SnapToSlideEdge |
bool? |
false |
MaxBackfaceHiddenSlides |
int? |
10 |
WatchOverflow |
bool? |
true |
WatchSlidesProgress |
bool? |
false |
OneWayMovement |
bool? |
false - not the same as AllowSlidePrev = false, see below |
CssMode |
bool? |
false - see below |
UpdateOnWindowResize |
bool? |
true |
ResizeObserver |
bool? |
true - see below |
Enabled |
bool? |
true |
Touch and interaction
| Option | Type | Swiper's default |
|---|---|---|
AllowTouchMove |
bool? |
true |
AllowSlideNext / AllowSlidePrev |
bool? |
true - see below |
GrabCursor |
bool? |
false |
TouchEventsTarget |
SwiperTouchEventsTarget? |
Wrapper |
TouchRatio |
double? |
1 |
TouchAngle |
double? |
45 |
SimulateTouch |
bool? |
true |
ShortSwipes / LongSwipes |
bool? |
true |
LongSwipesRatio |
double? |
0.5 |
LongSwipesMs |
int? |
300 |
FollowFinger |
bool? |
true |
Threshold |
double? |
5 |
TouchStartPreventDefault |
bool? |
true |
TouchStartForcePreventDefault |
bool? |
false |
TouchMoveStopPropagation |
bool? |
false |
EdgeSwipeDetection |
SwiperEdgeSwipeDetection? |
Disabled |
EdgeSwipeThreshold |
double? |
20 |
TouchReleaseOnEdges |
bool? |
false |
PassiveListeners |
bool? |
true |
Resistance |
bool? |
true |
ResistanceRatio |
double? |
0.85 |
PreventInteractionOnTransition |
bool? |
false |
NoSwiping |
bool? |
true |
NoSwipingClass |
string? |
swiper-no-swiping |
NoSwipingSelector |
string? |
null |
SwipeHandler |
string? |
null |
PreventClicks / PreventClicksPropagation |
bool? |
true |
SlideToClickedSlide |
bool? |
false |
FocusableElements |
string? |
form controls |
Loop and rewind
| Option | Type | Swiper's default |
|---|---|---|
Loop |
bool? |
false |
LoopAddBlankSlides |
bool? |
true |
LoopAdditionalSlides |
int? |
0 |
LoopPreventsSliding |
bool? |
true |
Rewind |
bool? |
false |
Breakpoints, observers and the rest
| Option | Type | Swiper's default |
|---|---|---|
Breakpoints |
Dictionary<string, SwiperOptions>? |
null - see below |
BreakpointsBase |
string? |
window - use the SwiperBreakpointsBase constants |
Observer |
bool? |
off, deviating from Swiper - see below |
ObserveParents / ObserveSlideChildren |
bool? |
false |
UserAgent / Url |
string? |
null - for prerendering on the server |
UniqueNavElements |
bool? |
true |
RunCallbacksOnInit |
bool? |
true |
InjectStyles |
string[]? |
null - see below |
InjectStylesUrls |
string[]? |
null |
ContainerModifierClass, SlideClass, SlideActiveClass, SlideVisibleClass, SlideFullyVisibleClass, SlideBlankClass, SlideNextClass, SlidePrevClass, WrapperClass, LazyPreloaderClass |
string? |
Swiper's own class names |
LazyPreloadPrevNext |
int? |
0 |
Modules
Each module is its own record, and each one that can be switched off outright has an implicit conversion from bool - so Pagination = true still reads well.
| Option | Type | Notes |
|---|---|---|
Navigation |
SwiperNavigationOptions? |
NextEl, PrevEl, AddIcons, HideOnClick, and the four class names |
Pagination |
SwiperPaginationOptions? |
Type, Clickable, DynamicBullets, DynamicMainBullets, BulletElement, ProgressbarOpposite, HideOnClick, the render templates, and thirteen class names |
Scrollbar |
SwiperScrollbarOptions? |
Draggable, Hide, SnapOnRelease, DragSize, and five class names |
Autoplay |
SwiperAutoplayOptions? |
Delay, StopOnLastSlide, DisableOnInteraction, ReverseDirection, WaitForTransition, PauseOnMouseEnter |
Keyboard |
SwiperKeyboardOptions? |
OnlyInViewport, PageUpDown, Speed |
Mousewheel |
SwiperMousewheelOptions? |
ForceToAxis, ReleaseOnEdges, Invert, Sensitivity, EventsTarget, ThresholdDelta, ThresholdTime, NoMousewheelClass |
FreeMode |
SwiperFreeModeOptions? |
Momentum, MomentumRatio, MomentumVelocityRatio, MomentumBounce, MomentumBounceRatio, MinimumVelocity, Sticky |
Grid |
SwiperGridOptions? |
Rows, Fill |
Zoom |
SwiperZoomOptions? |
MaxRatio, MinRatio, LimitToOriginalSize, PanOnMouseMove, Toggle, ContainerClass, ZoomedSlideClass |
Parallax |
SwiperParallaxOptions? |
Drive it from data-swiper-parallax attributes inside the slides |
A11y |
SwiperA11yOptions? |
On by default. Every message, role and ScrollOnFocus |
HashNavigation |
SwiperHashNavigationOptions? |
WatchState, ReplaceState. Needs SwiperSlide.Hash |
History |
SwiperHistoryOptions? |
Root, Key, ReplaceState, KeepQuery. Needs SwiperSlide.Hash |
Thumbs |
SwiperThumbsOptions? |
MultipleActiveThumbs, AutoScrollOffset, two class names. The strip itself goes on Thumbs |
Controller |
SwiperControllerOptions? |
Inverse, By. The controlled slider goes on Controller |
Virtual |
SwiperVirtualOptions? |
Cache, AddSlidesBefore, AddSlidesAfter, SlidesPerViewAutoSlideSize - read its remarks first |
FadeEffect, CubeEffect, FlipEffect, CoverflowEffect, CreativeEffect, CardsEffect |
records | Read by the matching Effect value |
A module options object with no Enabled set is taken by Swiper as enabled, so Autoplay = new() { Delay = 2000 } starts playing. Enabled = false is sent as a plain false rather than as an object, because Swiper Element builds a module's elements for any object it's given - before the module itself declines to run.
The two union types
Swiper has two parameters that take either a number or a string, and both are their own type here so the union survives:
new SwiperOptions
{
SlidesPerView = 2.5, // a count, fractions allowed
SlidesPerView = SwiperSlidesPerView.Auto, // ...or sized from the slides' own content
SpaceBetween = 16, // px
SpaceBetween = "10%", // ...or a percentage
}
Options are reactive
Changing Options pushes the members that moved to the live slider - the slider is updated in place, not rebuilt.
<Swiper Options="_options" />
@code {
private SwiperOptions _options = new() { SlidesPerView = 1 };
private void Widen() => _options = _options with { SlidesPerView = 3 };
}
Swiper re-applies most parameters on update. The ones it only reads while initializing - Effect, CssMode, the module option objects, the class names, InitialSlide - are ignored, and say so in the browser console rather than failing silently. Setting a member back to null leaves the last applied value, because Swiper has no way to unset a parameter back to its default.
Breakpoints
The value is a full SwiperOptions, because Swiper accepts the same shape at a breakpoint. Keys are widths in px, or ratios like @1.5.
new SwiperOptions
{
SlidesPerView = 1,
Breakpoints = new()
{
["480"] = new() { SlidesPerView = 2 },
["800"] = new() { SlidesPerView = 3 },
}
}
Not every parameter can change responsively; Swiper ignores the ones it can't re-apply, such as Loop and Effect.
OneWayMovement is not AllowSlidePrev = false
Both read as "the slider won't go back" if the only thing you try is swiping backwards, but they do different things and only one of them is a lock:
| Backwards drag | SlidePrev(), arrows, pagination |
|
|---|---|---|
OneWayMovement = true |
advances forward | still go back |
AllowSlidePrev = false |
does nothing | do nothing either |
OneWayMovement reinterprets the gesture: pull either way and the slider advances. AllowSlidePrev is a lock, and it applies to the API as much as to the gesture — with it off, SlidePrev() is a no-op. Reach for the first when the slides are a stack being flicked through, and the second when going back is genuinely not allowed.
ArmAnchor and UpdateAndAnchor ignore the lock, since a correction isn't navigation.
Observer is off by default
Swiper Element turns its MutationObserver on; this wrapper turns it off. With a framework re-rendering slide content - and especially with AutoHeight, whose height writes are themselves DOM mutations - it drives a costly update/height feedback loop. Call Update() explicitly when the slide collection changes, or set Observer = true for Swiper's behaviour.
CssMode trades features for smoothness
CssMode = true moves the slider onto the browser's native CSS Scroll Snap API, so scrolling runs on the compositor thread - dramatically smoother for heavy or tall slides. In exchange (these are Swiper's own limitations):
- mouse-drag does not work - wheel, trackpad and touch still do, exactly like a native scroller
Speedis ignored- transition start/end events do not fire, so use
OnSlideChangerather thanOnTransitionEnd - it is not compatible with
Loop
Programmatic moves are animated by the wrapper rather than by Swiper, because Swiper drives them with a native smooth scroll that scroll-snap-type: mandatory cancels outright.
ResizeObserver and A11y.ScrollOnFocus can undo a move you just made
Both of these Swiper behaviours end by re-anchoring onto the index Swiper is currently holding, and both are deferred by a frame. When your code moves the slider during the same interaction that triggered one of them, the correction is scheduled with the pre-move index, lands after your move, and silently undoes it.
ResizeObserver = falsedrops the per-element size observer. Window resizes still re-measure.A11y = new() { ScrollOnFocus = false }stops Swiper sliding a slide into view when something inside it takes focus - which otherwise pulls the slider back to the slide holding the button that just advanced it.
The wrapper already re-asserts a programmatic move if a resize tries to undo it while the move is still in flight, so reach for these when you'd rather Swiper didn't schedule the correction at all.
Reaching the shadow DOM
Swiper Element renders its controls into a shadow root. The navigation arrows are exposed as CSS parts:
swiper-container::part(button-next) { color: #0ea5a4; }
Anything deeper - the pagination bullets, for instance - needs InjectStyles, which is the same mechanism Swiper Element uses for its own CSS:
new SwiperOptions
{
Pagination = new() { Clickable = true },
InjectStyles = [".swiper-pagination-bullet { background: #0ea5a4; }"],
}
Pagination render templates
Swiper's renderBullet, renderFraction, renderProgressbar, renderCustom and the formatFraction* hooks are JavaScript functions it calls synchronously while rendering. A C# delegate can't answer synchronously - interop is asynchronous, and on Blazor Server it's a network round trip - so the wrapper takes templates and builds the functions on the JavaScript side.
Pagination = new()
{
RenderBulletTemplate = "<span class='{{className}}'>{{index}}</span>", // {{index}} is 1-based
FractionMinimumDigits = 2, // "03 / 12"
}
RenderFractionTemplate takes {{currentClass}} and {{totalClass}}, RenderProgressbarTemplate takes {{fillClass}}, and RenderCustomTemplate takes {{current}} and {{total}}.
Events
All 77 of Swiper's events have a callback. Nothing is listened for until you assign one - the component works out which callbacks have a delegate and tells the interop module only those names, so an unwired event costs no DOM listener and no interop call.
<Swiper OnReachEnd="LoadMore" OnUserSlideChange="LoadPageFor" OnZoomChange="z => _scale = z.Scale" />
| Group | Callbacks |
|---|---|
| Lifecycle | OnBeforeInit, OnInit, OnAfterInit, OnReady, OnBeforeDestroy, OnDestroy |
| Slide changes | OnSlideChange, OnUserSlideChange, OnBeforeSlideChangeStart, OnActiveIndexChange, OnRealIndexChange, OnSnapIndexChange |
| Transitions | OnTransitionStart, OnTransitionEnd, OnBeforeTransitionStart, OnSlideChangeTransitionStart/End, OnSlideNextTransitionStart/End, OnSlidePrevTransitionStart/End, OnSlideResetTransitionStart/End |
| Pointer | OnTouchStart, OnTouchMove, OnTouchMoveOpposite, OnTouchEnd, OnSliderMove, OnSliderFirstMove, OnClick, OnTap, OnDoubleTap, OnDoubleClick |
| Position | OnProgress, OnSetTranslate, OnSetTransition, OnReachBeginning, OnReachEnd, OnToEdge, OnFromEdge, OnMomentumBounce |
| Layout | OnResize, OnBeforeResize, OnUpdate, OnSlidesUpdated, OnSlidesLengthChange, OnSlidesGridLengthChange, OnSnapGridLengthChange, OnBreakpoint, OnChangeDirection, OnOrientationChange, OnObserverUpdate, OnLock, OnUnlock |
| Loop | OnBeforeLoopFix, OnLoopFix |
| Autoplay | OnAutoplay, OnAutoplayStart, OnAutoplayStop, OnAutoplayPause, OnAutoplayResume, OnAutoplayTimeLeft |
| Navigation & pagination | OnNavigationNext, OnNavigationPrev, OnNavigationShow, OnNavigationHide, OnPaginationRender, OnPaginationUpdate, OnPaginationShow, OnPaginationHide |
| Scrollbar | OnScrollbarDragStart, OnScrollbarDragMove, OnScrollbarDragEnd |
| Other modules | OnKeyPress, OnScroll (mousewheel), OnZoomChange, OnHashChange, OnHashSet, OnVirtualUpdate |
Swiper hands several events a DOM element or DOM event, neither of which can cross the interop boundary, so what arrives is the part you can act on: SwiperPointerEventArgs (client coordinates and the clicked slide index), SwiperAutoplayTimeLeft, SwiperZoomChange, SwiperMousewheelScroll.
Events that fire every frame
OnProgress, OnSetTranslate, OnSetTransition, OnSliderMove, OnTouchMove, OnTouchMoveOpposite, OnAutoplayTimeLeft, OnZoomChange and OnScroll fire per animation frame. On Blazor Server that's a network round trip per frame, which is what EventThrottle is for:
<Swiper EventThrottle="TimeSpan.FromMilliseconds(50)" OnProgress="p => _progress = p" />
The first event of a burst is always delivered, so a throttled OnProgress still sees a drag start immediately.
Prefer OnUserSlideChange when you react by changing state
OnSlideChange reports every change - including the one your own SlideTo call just caused, and the echo Loop mode emits for the slide it left once a programmatic move finishes. A host that reacts to a change by updating its own state will feed its own move straight back into itself, and by index alone the two are indistinguishable.
OnUserSlideChange reports only the changes the user dragged. A plain tap raises neither, so a button inside a slide is never mistaken for a swipe. The slider keeps moving after the pointer is released, so treat the interaction as finished on OnTransitionEnd rather than on release.
Programmatic control
Capture the component with @ref. Every method is a no-op until the component has rendered and its JS module has loaded, so calling one before OnReady does nothing rather than throwing.
| Member | Description |
|---|---|
ActiveIndex |
The active slide's logical index. Two-way bindable. |
SlideTo(index, speed?) |
Transition to a slide. |
SlideNext(speed?) / SlidePrev(speed?) |
Step. |
SlideReset(speed?) |
Settle back onto the current slide. |
SlideToClosest(speed?) |
Snap to whichever slide is nearest. |
SlideToClickedSlide() |
Move to the slide the user last clicked. |
Update() |
Recalculate after the slide collection changed. |
UpdateSize(), UpdateSlides(), UpdateProgress(), UpdateSlidesClasses(), UpdateAutoHeight(speed) |
The narrower recalculations. |
UpdateAndAnchor(index) / ArmAnchor(index) |
Keep the position across a slide-collection change - see below. |
SetAllowSlideNext(bool) / SetAllowSlidePrev(bool) / SetAllowTouchMove(bool) |
Runtime locks. |
Enable() / Disable() |
Whether the slider responds to anything at all. |
SetProgress(progress, speed?) |
Move to a position rather than to a slide. |
ChangeDirection(direction) |
Switch axis at runtime. |
ChangeLanguageDirection(isRightToLeft) |
Switch between LTR and RTL. |
TranslateTo(translate, speed) / GetTranslateAsync() |
The track's raw offset. |
DetachEvents() / AttachEvents() |
Stop and restart Swiper's own listeners. |
GetStateAsync() |
Everything Swiper knows about itself, in one call. |
Modules with several methods of their own are grouped:
await _swiper.Autoplay.Start(); // Stop, Pause, Resume
await _swiper.Zoom.Toggle(); // In, Out, Enable, Disable
await _swiper.Keyboard.Disable(); // Enable
await _swiper.Mousewheel.Enable(); // Disable
await _swiper.Virtual.Update(force: true);
await _swiper.Slides.Append("<swiper-slide>...</swiper-slide>"); // Prepend, Insert, Remove, RemoveAll
Slides is Swiper's own manipulation API. It writes slide elements Blazor didn't render and doesn't know about, so the next render that touches the collection will fight it - it's an escape hatch for a host that owns the slider outright. When Blazor renders the slides, keep the @foreach and use the anchoring methods below.
Reading the state
var state = await _swiper.GetStateAsync();
// ActiveIndex, RawActiveIndex, PreviousIndex, SnapIndex, SlidesCount,
// IsBeginning, IsEnd, IsLocked, IsAnimating, IsEnabled, Progress, Translate,
// Width, Height, SwipeDirection, CurrentBreakpoint, VisibleSlideIndexes,
// IsAutoplayRunning, IsAutoplayPaused, ZoomScale
One call rather than a round trip per value, which on Blazor Server is a network hop each. VisibleSlideIndexes is empty unless WatchSlidesProgress is on, since that's what measures them.
Keeping the position when slides are added or removed
When Blazor itself adds or removes <SwiperSlide> elements, Swiper's transform still points at the old offset. Removing a slide that sits before the active one shifts every later slide sideways, so the wrong slide is on screen until Swiper is told. Update() isn't enough here: it finishes by sliding to the index it already held, and that index addresses a different slide once the collection changed.
UpdateAndAnchor(index)recalculates and settles onindexin one operation. Use it when your code changes the collection and knows where it wants to end up.ArmAnchor(index)is for when Blazor owns the mutation. Call it before the change: it arms a one-shotMutationObserverwhose callback runs after Blazor's DOM patch but before the next paint, so the correction lands in the same frame. Correcting from a call made after the change is a race the browser can win by painting first.
// About to remove a slide that sits before the active one.
await _swiper!.ArmAnchor(_activeIndex - 1);
_slides.RemoveAt(0);
Both ignore AllowSlideNext / AllowSlidePrev - a correction isn't navigation, so your swipe locks can't veto it.
Slides
| Parameter | Type | Description |
|---|---|---|
Index |
int? |
The slide's position, for SlideContent. See below. |
Zoom |
bool |
Wraps the content in Swiper's zoom container, which is what Options.Zoom scales. |
ZoomMaxRatio |
double? |
A zoom factor for this slide alone. |
Lazy |
bool |
Defers this slide's images. Mark the images loading="lazy" too. |
Hash |
string? |
This slide's key for HashNavigation and History. |
AutoplayDelay |
int? |
How long autoplay holds this slide, overriding the slider's delay. |
VirtualIndex |
int? |
This slide's index for the Virtual module. |
Swiper reads all of these off the slide element itself, which is why they're parameters here rather than members of SwiperOptions.
Per-slide state
SlideContent hands the content the slide's own state, so it can render differently while it's the active one - pausing a video on the slides that aren't, for instance:
@foreach (var (item, index) in _items.Select((x, i) => (x, i)))
{
<SwiperSlide Index="index">
<SlideContent Context="slide">
<video autoplay="@slide.IsActive" src="@item.Url" />
</SlideContent>
</SwiperSlide>
}
SwiperSlideContext carries Index, IsActive, IsNext and IsPrevious, derived from the slider's active index rather than read back out of Swiper - so it costs no interop and is available during render. Plain ChildContent is untouched and doesn't pay for any of it.
Index is only needed for this. Left null, a slide takes its position from the order the slides registered in, which is right for a fixed list and for one that only grows at the end, but not for one that inserts in the middle.
Thumbnails and synced sliders
Name the other slider by CSS selector. It's the order-independent route and the one to prefer:
<Swiper Options="new() { Thumbs = new() { Swiper = "#thumbs", MultipleActiveThumbs = false } }">
...
</Swiper>
<Swiper id="thumbs" Options="new() { SlidesPerView = 5, WatchSlidesProgress = true, SlideToClickedSlide = true }">
...
</Swiper>
Controller = new() { Control = "#captions", Inverse = true } works the same way.
Why a selector rather than a @ref? Because @ref is assigned during the render that creates the component — after the sibling's Thumbs="_thumbs" attribute has already been evaluated. On first paint it's still null, and it only arrives if the host happens to re-render. A selector has no such problem: the wiring is resolved on the JavaScript side and waits for whichever slider initializes last.
The Thumbs and Controller component parameters still exist for hosts that do re-render (they're wired as soon as the companion reports itself initialized), but the selector is what the stories use.
Swiper's own thumbs and controller modules take a live instance and nothing else, so both routes end in the wrapper handing one over — the selector is resolved by us, not by Swiper.
Reaching anything else
SwiperOptions covers every parameter Swiper Element accepts, so this is rarely needed - but any attribute you put on <Swiper> that isn't a component parameter is forwarded to the underlying <swiper-container>, which reads it as a parameter when it initializes. The element also parses nested parameters from attributes, so pagination-clickable="true" reaches pagination.clickable.
<Swiper Options="new() { SlidesPerView = 2 }" pagination-clickable="true" />
Vertical direction needs a height
A vertical Swiper sizes its slides from the <Swiper> element's own height, which defaults to its content height. Give it an explicit height, or it collapses and the slides stack:
<Swiper Options="new() { Direction = SwiperDirection.Vertical }" style="height: 300px;">
Upgrading from 14.0.6.1
The API grew from a curated subset to Swiper's whole surface, which meant some breaks. All of them are compile-time.
| Was | Now |
|---|---|
Direction = "horizontal" |
SwiperDirection is an enum. SwiperDirection.Horizontal still compiles; a string literal doesn't. |
Pagination = true |
Still compiles - the module options records convert implicitly from bool. |
AllowSlideNext = true and friends |
Now bool?. Assignments still compile; reads need == true or ?? true. |
A11y = new SwiperA11yOptions { ScrollOnFocus = false } |
Unchanged, and the record now carries every other a11y parameter too. |
ActiveIndex (read-only property) |
A two-way bindable parameter. Reads are unchanged. |
OnSlideChangeInternal, OnReachEndInternal, ... |
Collapsed into one OnSwiperEventInternal dispatcher. These were never meant to be called from your code. |
<SwiperSlide> content |
Unchanged. Per-slide state is the new SlideContent, which is opt-in. |
OnSlideChange etc. |
Unchanged - but they're now opt-in, so an unassigned callback is no longer listened for at all. |
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.0)
-
net6.0
- Microsoft.AspNetCore.Components.Web (>= 6.0.0)
-
net7.0
- Microsoft.AspNetCore.Components.Web (>= 7.0.0)
-
net8.0
- Microsoft.AspNetCore.Components.Web (>= 8.0.0)
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
BREAKING, and a much larger API. SwiperOptions now mirrors every one of Swiper's parameters - all 95 top-level ones plus the 22 nested module option records - and every member is nullable, so an unset member is the only signal for "keep Swiper's default". All 77 of Swiper's events now have a callback, subscribed only when you assign one, so an unwired event costs no listener and no interop. ActiveIndex is two-way bindable (@bind-ActiveIndex), as is IsAutoplayRunning. Options is now reactive: changing it pushes the members that moved to the live slider. Adds GetStateAsync, the full navigation and update method set, Autoplay/Zoom/Keyboard/Mousewheel/Slides/Virtual controllers, Thumbs and Controller cross-instance wiring, slide-level Zoom/Lazy/Hash/AutoplayDelay/VirtualIndex, per-slide state through SlideContent, declarative pagination templates, and EventThrottle for the events that fire per animation frame. Breaking: Direction is now the SwiperDirection enum (SwiperDirection.Horizontal still compiles, a literal "horizontal" does not); Pagination/Navigation/Scrollbar/Keyboard/Mousewheel are option records with an implicit conversion from bool, so `= true` still compiles; previously non-nullable options are nullable; SwiperSlide.ChildContent stays plain and per-slide state moved to SlideContent; the per-event JSInvokable methods collapsed into one dispatcher. Same vendored Swiper 14.0.6 bundle.