Carousel
A set of slides, moved between with Previous, Next or a row of dots. Never advances on its own.
Code
The three render the same class names, the same ARIA and the same data attributes. That is checked when this site builds: a class in any of these samples that neither the markup contract nor the shipped stylesheet defines fails the build.
<div class="wsu-carousel" data-wsu-carousel role="region" aria-roledescription="carousel" aria-label="Campus highlights">
<div class="wsu-carousel__slides">
<div class="wsu-carousel__slide" role="tabpanel" aria-roledescription="slide"
id="highlight-panel-1" aria-labelledby="highlight-tab-1" tabindex="0">
…
</div>
<div class="wsu-carousel__slide" role="tabpanel" aria-roledescription="slide"
id="highlight-panel-2" aria-labelledby="highlight-tab-2" tabindex="0" hidden>
…
</div>
</div>
<div class="wsu-carousel__controls">
<button type="button" class="wsu-carousel__prev" aria-label="Previous slide">…</button>
<div class="wsu-carousel__dots" role="tablist" aria-label="Slides in Campus highlights">
<button class="wsu-carousel__tab" type="button" role="tab"
id="highlight-tab-1" aria-controls="highlight-panel-1" aria-selected="true" tabindex="0">
<span class="wsu-sr-only">Slide 1: Apply for financial aid</span>
</button>
<button class="wsu-carousel__tab" type="button" role="tab"
id="highlight-tab-2" aria-controls="highlight-panel-2" aria-selected="false" tabindex="-1">
<span class="wsu-sr-only">Slide 2: Extended advising hours</span>
</button>
</div>
<button type="button" class="wsu-carousel__next" aria-label="Next slide">…</button>
</div>
</div><x-wsu::carousel label="Campus highlights" :slides="[
['id' => 'aid', 'label' => 'Apply for financial aid'],
['id' => 'advising', 'label' => 'Extended advising hours'],
]">
<x-slot:aid>The priority deadline for Fall 2027 is March 1.</x-slot:aid>
<x-slot:advising>Advising is open until 7 PM on Tuesdays this month.</x-slot:advising>
</x-wsu::carousel><Carousel
v-model="highlight"
label="Campus highlights"
:slides="[
{ id: 'aid', label: 'Apply for financial aid' },
{ id: 'advising', label: 'Extended advising hours' },
]"
>
<template #aid>The priority deadline for Fall 2027 is March 1.</template>
<template #advising>Advising is open until 7 PM on Tuesdays this month.</template>
</Carousel>Why it works this way
- The dots are Tabs under a different name: role="tablist", a roving tabindex, and the same Left/Right/Home/End movement (2.1.1). Previous and Next move the same selection a dot does, through the same selectTab() the tab set uses in packages/css/src/theme.js, so an arrow key on a dot and a press of Next can never disagree about which slide is current.
- No autoplay, and therefore no pause control. WCAG 2.2.2 requires a way to stop content that moves without being asked; a carousel that only moves because someone pressed Previous, Next or a dot never triggers that. TextRotate is the component for content that should rotate unattended, and it carries the pause control that decision requires.
- Public template only, and that absence is a decision rather than an oversight: a promotional carousel is for someone with no task open yet, which describes a prospective or browsing student on the public catalog and not someone already partway through registering for a course.
Accessibility
- role="tablist" with a name, role="tab" with aria-selected and aria-controls, role="tabpanel" with aria-labelledby (4.1.2), the same pairing Tabs uses.
- aria-roledescription="carousel" and "slide" announce this as a carousel rather than a generic tab set, without changing the underlying role or its keyboard contract.
- Roving tabindex on the dots, so Tab enters and leaves the set rather than walking every dot (2.1.1).
- Nothing here moves without being asked, so WCAG 2.2.2 does not apply and there is no pause control to omit by mistake.
Success criteria in brackets refer to WCAG 2.2. Every one of them is checked by axe-core in both themes at three viewport widths before this page can be published. See Accessibility for what the gates are.
Props
Blade
| Attribute | Default | What it does |
|---|---|---|
slides | [] | [{ id, label }]. Each id becomes a slot name. |
label | required | Throws if omitted, or the region announces no name. |
Vue
| Prop | Type and default | What it does |
|---|---|---|
slides | Array, [] | [{ id, label }]. |
modelValue | String, '' | Selected slide id. |
label | String, required | Names the carousel region. |
In the markup contract
contract/markup-contract.json is extracted from the two static templates and every package tests against it. This is what it records for .wsu-carousel.
| Template | Elements | Accessibility attributes | Uses |
|---|---|---|---|
| public apps/examples/static-html/public-app.html | div | aria-labelrole | 1 |