M
Marai.UI
v1.0.0-alpha.7

Carousel

A headless Blazor carousel that owns state, navigation, timing, and ARIA — nothing else. Consumers compose controls and indicators using unstyled behavior components and style everything with Tailwind or their own classes.

Overview

MCarousel manages which slide is active and exposes navigation through cascaded context. It renders no built-in controls, indicators, or CSS classes. Slides always render in the DOM; visibility is controlled through the data-state="active|inactive" attribute so consumers can drive it with Tailwind's data-[state=inactive]:hidden or any other utility.

Three unstyled behavior components are available for composition inside a carousel: MCarouselPrevious, MCarouselNext, and MCarouselIndicator. Each wires up to the carousel context automatically and adds no default styling.

Usage

Add the namespace import to use the component.

razor
@using Marai.UI.Components.MCarousel

Basic Usage

A carousel with three slides and composed prev/next controls. Inactive slides are hidden via data-[state=inactive]:hidden on the slide's Class.

Preview
Slide 1
razor
<MCarousel Class="relative w-full overflow-hidden" AriaLabel="Basic carousel">
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-muted text-muted-foreground text-sm font-medium">
            Slide 1
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-secondary text-secondary-foreground text-sm font-medium">
            Slide 2
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-accent text-accent-foreground text-sm font-medium">
            Slide 3
        </div>
    </MCarouselSlide>
    <MCarouselPrevious aria-label="Previous slide"
        Class="absolute left-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/80 text-foreground hover:bg-background">
        ‹
    </MCarouselPrevious>
    <MCarouselNext aria-label="Next slide"
        Class="absolute right-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/80 text-foreground hover:bg-background">
        ›
    </MCarouselNext>
</MCarousel>

Examples

Indicator Navigation

Use MCarouselIndicator with a required Index to add dot navigation. Each indicator exposes data-state="active|inactive", so active state styling is driven by data-[state=active]: utilities.

Preview
Slide 1 of 4
razor
<MCarousel Class="relative w-full overflow-hidden" AriaLabel="Carousel with indicators">
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-muted text-muted-foreground text-sm font-medium">
            Slide 1 of 4
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-secondary text-secondary-foreground text-sm font-medium">
            Slide 2 of 4
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-accent text-accent-foreground text-sm font-medium">
            Slide 3 of 4
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-muted text-muted-foreground text-sm font-medium">
            Slide 4 of 4
        </div>
    </MCarouselSlide>
    <div class="absolute bottom-3 left-1/2 -translate-x-1/2 flex items-center gap-1.5" role="tablist" aria-label="Slide navigation">
        <MCarouselIndicator Index="0" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
        <MCarouselIndicator Index="1" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
        <MCarouselIndicator Index="2" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
        <MCarouselIndicator Index="3" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
    </div>
    <MCarouselPrevious aria-label="Previous slide"
        Class="absolute left-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/80 text-foreground hover:bg-background">
        ‹
    </MCarouselPrevious>
    <MCarouselNext aria-label="Next slide"
        Class="absolute right-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/80 text-foreground hover:bg-background">
        ›
    </MCarouselNext>
</MCarousel>

Auto-Advance

Set AutoAdvance="true" to cycle through slides automatically. Use Interval (in milliseconds) to control the delay between advances. Auto-advance pauses when the user hovers over or focuses within the carousel, and resumes when they leave.

Preview
Auto-advances every 3 s — pauses on hover or focus
razor
<MCarousel AutoAdvance="true" Interval="3000" Class="relative w-full overflow-hidden" AriaLabel="Auto-advancing carousel">
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-muted text-muted-foreground text-sm font-medium">
            Auto-advances every 3 s — pauses on hover or focus
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-secondary text-secondary-foreground text-sm font-medium">
            Slide 2
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-accent text-accent-foreground text-sm font-medium">
            Slide 3
        </div>
    </MCarouselSlide>
    <div class="absolute bottom-3 left-1/2 -translate-x-1/2 flex items-center gap-1.5">
        <MCarouselIndicator Index="0" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
        <MCarouselIndicator Index="1" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
        <MCarouselIndicator Index="2" Class="h-2 w-2 rounded-full transition-all duration-300 bg-primary/30 data-[state=active]:w-6 data-[state=active]:bg-primary/80" />
    </div>
    <MCarouselPrevious aria-label="Previous slide"
        Class="absolute left-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/80 text-foreground hover:bg-background">
        ‹
    </MCarouselPrevious>
    <MCarouselNext aria-label="Next slide"
        Class="absolute right-2 top-1/2 -translate-y-1/2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/80 text-foreground hover:bg-background">
        ›
    </MCarouselNext>
</MCarousel>

Custom Controls

MCarouselPrevious and MCarouselNext render a native <button> with no default styling. Pass any content — text, icons, or SVGs — as ChildContent and style with Class.

Preview
Controls styled with your own classes
razor
<MCarousel Class="relative w-full overflow-hidden" AriaLabel="Carousel with custom controls">
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-medium">
            Controls styled with your own classes
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-secondary text-secondary-foreground text-sm font-medium">
            Slide 2
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-48 items-center justify-center rounded-lg bg-muted text-muted-foreground text-sm font-medium">
            Slide 3
        </div>
    </MCarouselSlide>
    <MCarouselPrevious aria-label="Previous slide"
        Class="absolute left-2 top-1/2 -translate-y-1/2 rounded px-3 py-1 text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90">
        ← Prev
    </MCarouselPrevious>
    <MCarouselNext aria-label="Next slide"
        Class="absolute right-2 top-1/2 -translate-y-1/2 rounded px-3 py-1 text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90">
        Next →
    </MCarouselNext>
</MCarousel>

Fully Styled

A complete example using only Tailwind utilities — gradient slides, pill indicators, and frosted-glass prev/next buttons.

Preview

Slide One

Fully styled with Tailwind utilities

razor
<MCarousel Class="relative w-full overflow-hidden rounded-xl shadow-lg" AriaLabel="Fully styled carousel">
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-64 items-center justify-center bg-gradient-to-br from-primary to-primary/60 text-primary-foreground">
            <div class="text-center">
                <p class="text-2xl font-bold">Slide One</p>
                <p class="mt-1 text-sm opacity-80">Fully styled with Tailwind utilities</p>
            </div>
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-64 items-center justify-center bg-gradient-to-br from-secondary to-secondary/60 text-secondary-foreground">
            <div class="text-center">
                <p class="text-2xl font-bold">Slide Two</p>
                <p class="mt-1 text-sm opacity-80">Style controlled entirely by the consumer</p>
            </div>
        </div>
    </MCarouselSlide>
    <MCarouselSlide Class="data-[state=inactive]:hidden">
        <div class="flex h-64 items-center justify-center bg-gradient-to-br from-accent to-accent/60 text-accent-foreground">
            <div class="text-center">
                <p class="text-2xl font-bold">Slide Three</p>
                <p class="mt-1 text-sm opacity-80">No default classes from the component</p>
            </div>
        </div>
    </MCarouselSlide>
    <div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
        <MCarouselIndicator Index="0" Class="h-2 w-2 rounded-full transition-all duration-300 bg-white/40 data-[state=active]:w-6 data-[state=active]:bg-white" />
        <MCarouselIndicator Index="1" Class="h-2 w-2 rounded-full transition-all duration-300 bg-white/40 data-[state=active]:w-6 data-[state=active]:bg-white" />
        <MCarouselIndicator Index="2" Class="h-2 w-2 rounded-full transition-all duration-300 bg-white/40 data-[state=active]:w-6 data-[state=active]:bg-white" />
    </div>
    <MCarouselPrevious aria-label="Previous slide"
        Class="absolute left-3 top-1/2 -translate-y-1/2 inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/20 text-white backdrop-blur-sm hover:bg-black/40 transition-colors">
        ‹
    </MCarouselPrevious>
    <MCarouselNext aria-label="Next slide"
        Class="absolute right-3 top-1/2 -translate-y-1/2 inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/20 text-white backdrop-blur-sm hover:bg-black/40 transition-colors">
        ›
    </MCarouselNext>
</MCarousel>

Accessibility

  • The carousel root is a <section> with role="region" and an accessible label set via AriaLabel (defaults to "Carousel").
  • Every MCarouselSlide is always in the DOM with role="group", aria-roledescription="slide", and aria-hidden="true|false" to communicate visibility to assistive technologies.
  • MCarouselIndicator buttons have a generated aria-label ("Go to slide N") and data-state for active state. Wrap them in a <div role="tablist" aria-label="Slide navigation"> when using them as tab-style navigation.
  • MCarouselPrevious and MCarouselNext are native buttons. Add aria-label via the attribute — for example, aria-label="Previous slide".
  • Use AriaLabel to provide a meaningful name when the default "Carousel" is not descriptive enough.

API Reference

MCarousel

Parameter Type Default Description
ChildContent RenderFragment? null Slides and composed controls placed inside the carousel.
AutoAdvance bool false When true, slides advance automatically on the configured Interval.
Interval int 5000 Milliseconds between auto-advance steps. Only used when AutoAdvance is true.
AriaLabel string? "Carousel" Accessible label for the carousel region.
Class string? null CSS classes applied to the root <section>.
AdditionalAttributes Dictionary<string, object>? null Arbitrary HTML attributes forwarded to the root element.

MCarouselSlide

Parameter Type Default Description
ChildContent RenderFragment? null The content of this slide.
Class string? null CSS classes applied to the slide wrapper. Use data-[state=inactive]:hidden to hide inactive slides.
AdditionalAttributes Dictionary<string, object>? null Arbitrary HTML attributes forwarded to the slide element.

MCarouselPrevious / MCarouselNext

Parameter Type Default Description
ChildContent RenderFragment? null Button content — icon, text, or any markup.
Class string? null CSS classes applied to the button element.
AdditionalAttributes Dictionary<string, object>? null Arbitrary HTML attributes forwarded to the button — use for aria-label.

MCarouselIndicator

Parameter Type Default Description
Index int required Zero-based slide index this indicator navigates to.
ChildContent RenderFragment? null Optional button content. Leave empty for a pure CSS indicator.
Class string? null CSS classes applied to the button. Use data-[state=active]: and data-[state=inactive]: modifiers for state-driven styling.
AdditionalAttributes Dictionary<string, object>? null Arbitrary HTML attributes forwarded to the button.