fix(Carousel): improve accessibility

Resolves #4494
This commit is contained in:
Benjamin Canac
2025-07-10 14:18:34 +02:00
parent a813ea700e
commit 55e06e97e7
55 changed files with 75 additions and 22 deletions

View File

@@ -256,6 +256,7 @@ const scrollSnaps = ref<number[]>([])
function onInit(api: EmblaCarouselType) {
scrollSnaps.value = api?.scrollSnapList() || []
}
function onSelect(api: EmblaCarouselType) {
canScrollNext.value = api?.canScrollNext() || false
canScrollPrev.value = api?.canScrollPrev() || false
@@ -300,8 +301,7 @@ defineExpose({
<div
v-for="(item, index) in items"
:key="index"
role="group"
aria-roledescription="slide"
v-bind="dots ? { role: 'tabpanel' } : { 'role': 'group', 'aria-roledescription': 'slide' }"
:class="ui.item({ class: [props.ui?.item, isCarouselItem(item) && item.ui?.item, isCarouselItem(item) && item.class] })"
>
<slot :item="item" :index="index" />
@@ -333,14 +333,15 @@ defineExpose({
/>
</div>
<div v-if="dots" :class="ui.dots({ class: props.ui?.dots })">
<div v-if="dots" role="tablist" :aria-label="t('carousel.dots')" :class="ui.dots({ class: props.ui?.dots })">
<template v-for="(_, index) in scrollSnaps" :key="index">
<button
type="button"
role="tab"
:aria-label="t('carousel.goto', { slide: index + 1 })"
:aria-selected="selectedIndex === index"
:class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })"
:data-state="selectedIndex === index ? 'active' : undefined"
:aria-current="selectedIndex === index ? true : undefined"
@click="scrollTo(index)"
/>
</template>

View File

@@ -40,7 +40,8 @@ export default defineLocale<Messages>({
carousel: {
prev: 'السابق',
next: 'التالي',
goto: 'الذهاب إلي شريحة {slide}'
dots: 'اختر الشريحة المراد عرضها',
goto: 'الذهاب إلى شريحة {slide}'
},
modal: {
close: 'إغلاق'

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Əvvəlki',
next: 'Növbəti',
dots: 'Göstərmək üçün slayd seçin',
goto: 'Slayd {slide} keç'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Назад',
next: 'Напред',
dots: 'Изберете слайд за показване',
goto: 'Отидете на слайд {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'পূর্ববর্তী',
next: 'পরবর্তী',
dots: 'প্রদর্শনের জন্য স্লাইড নির্বাচন করুন',
goto: 'স্লাইড {slide} এ যান'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Anterior',
next: 'Següent',
dots: 'Tria la diapositiva a mostrar',
goto: 'Anar a la diapositiva {slide}'
},
modal: {

View File

@@ -38,8 +38,9 @@ export default defineLocale<Messages>({
close: 'داخستن'
},
carousel: {
prev: 'پێشوو',
prev: 'پێشووی',
next: 'داهاتوو',
dots: 'سلایدێک هەڵبژێرە بۆ پیشاندان',
goto: 'بڕۆ بۆ سلایدی {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Předchozí',
next: 'Další',
dots: 'Vyberte snímek k zobrazení',
goto: 'Přejít na {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Forrige',
next: 'Næste',
dots: 'Vælg dias til visning',
goto: 'Gå til slide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Zurück',
next: 'Weiter',
dots: 'Folie zur Anzeige auswählen',
goto: 'Gehe zu {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Προηγούμενο',
next: 'Επόμενο',
dots: 'Επιλέξτε διαφάνεια για εμφάνιση',
goto: 'Μετάβαση στη διαφάνεια {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Prev',
next: 'Next',
dots: 'Choose slide to display',
goto: 'Go to slide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Anterior',
next: 'Siguiente',
dots: 'Elegir diapositiva a mostrar',
goto: 'Ir a la diapositiva {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Eel',
next: 'Järg',
dots: 'Valige kuvatav slaid',
goto: 'Mine slaidile {slide}'
},
modal: {

View File

@@ -40,6 +40,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'قبلی',
next: 'بعدی',
dots: 'اسلاید مورد نظر برای نمایش را انتخاب کنید',
goto: 'رفتن به اسلاید {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Edellinen',
next: 'Seuraava',
dots: 'Valitse näytettävä dia',
goto: 'Siirry sivulle {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Précédent',
next: 'Suivant',
dots: 'Choisir la diapositive à afficher',
goto: 'Aller à {slide}'
},
modal: {

View File

@@ -38,6 +38,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'הקודם',
next: 'הבא',
dots: 'בחר שקופית להצגה',
goto: 'מעבר ל {slide}'
},
modal: {

View File

@@ -39,7 +39,8 @@ export default defineLocale<Messages>({
carousel: {
prev: 'पिछला',
next: 'अगला',
goto: 'स्लाइड {slide} पर जाएँ'
dots: 'प्रदर्शित करने के लिए स्लाइड चुनें',
goto: 'स्लाइड {slide} पर जाएं'
},
modal: {
close: 'बंद करें'

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Előző',
next: 'Következő',
dots: 'Válassza ki a megjelenítendő diát',
goto: 'Ugrás ide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Հետ',
next: 'Առաջ',
dots: 'Ընտրեք ցուցադրելու սլայդը',
goto: 'Անցնել {slide}-ին'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Sebelumnya',
next: 'Berikutnya',
dots: 'Pilih slide untuk ditampilkan',
goto: 'Pergi ke slide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Precedente',
next: 'Successiva',
dots: 'Scegli diapositiva da visualizzare',
goto: 'Vai alla slide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: '前へ',
next: '次へ',
dots: '表示するスライドを選択',
goto: 'スライド {slide} に移動'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Алдыңғы',
next: 'Келесі',
dots: 'Көрсету үшін слайдты таңдаңыз',
goto: '{slide} слайдқа өту'
},
modal: {

View File

@@ -39,7 +39,8 @@ export default defineLocale<Messages>({
carousel: {
prev: 'មុន',
next: 'បន្ទាប់',
goto: 'លោតទៅកាន់ស្លាយ {slide}'
dots: 'ជ្រើសរើស​ស្លាយ​ដើម្បី​បង្ហាញ',
goto: 'ឡើងទៅស្លាយ {slide}'
},
modal: {
close: 'បិទ'

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: '이전',
next: '다음',
dots: '표시할 슬라이드 선택',
goto: '{slide} 페이지로 이동'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Алдыңкы',
next: 'Кийинки',
dots: 'Көрсөтүү үчүн слайдды тандаңыз',
goto: '{slide} слайдга өтүү'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Präz.',
next: 'Näch.',
dots: 'Wielt Dia fir ze weisen',
goto: 'Gitt op d\'Slide {Slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Atgal',
next: 'Pirmyn',
dots: 'Pasirinkite skaidrę rodymui',
goto: 'Eiti į skaidrę {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Өмнөх',
next: 'Дараах',
dots: 'Харуулах слайдыг сонгоно уу',
goto: '{slide}-р хуудсанд шилжих'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Sebelum',
next: 'Seterusnya',
dots: 'Pilih slaid untuk dipaparkan',
goto: 'Pergi ke slaid {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Forrige',
next: 'Neste',
dots: 'Velg lysbilde som skal vises',
goto: 'Gå til lysbilde {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Vorige',
next: 'Volgende',
dots: 'Kies dia om weer te geven',
goto: 'Ga naar dia {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Poprzedni',
next: 'Następny',
dots: 'Wybierz slajd do wyświetlenia',
goto: 'Idź do {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Anterior',
next: 'Próximo',
dots: 'Escolher slide para exibir',
goto: 'Ir ao diapositivo {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Anterior',
next: 'Próximo',
dots: 'Escolher slide para exibir',
goto: 'Ir para a slide {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Anterior',
next: 'Următor',
dots: 'Alegeți diapozitivul de afișat',
goto: 'Mergi la diapozitivul {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Назад',
next: 'Далее',
dots: 'Выберите слайд для отображения',
goto: 'Перейти к {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Predchádzajúci',
next: 'Nasledujúci',
dots: 'Vyberte snímku na zobrazenie',
goto: 'Prejsť na {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Nazaj',
next: 'Naprej',
dots: 'Izberite diapozitiv za prikaz',
goto: 'Pojdi na {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Föregående',
next: 'Nästa',
dots: 'Välj bild att visa',
goto: 'Gå till {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'ย้อนกลับ',
next: 'ถัดไป',
dots: 'เลือกสไลด์ที่จะแสดง',
goto: 'ไปที่ {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Қаблӣ',
next: 'Баъдӣ',
dots: 'Слайдро барои намоиш интихоб кунед',
goto: 'Ба слайди {slide} гузаред'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Önceki',
next: 'Sonraki',
dots: 'Görüntülenecek slaydı seçin',
goto: '{slide}. slayda git'
},
modal: {

View File

@@ -40,6 +40,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'ئالدىنقى بەت',
next: 'كېيىنكى بەت',
dots: 'كۆرسىتىدىغان سلايدنى تاللاڭ',
goto: '{slide}-بەتكە ئاتلاش'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Назад',
next: 'Далі',
dots: 'Виберіть слайд для відображення',
goto: 'Перейти до {slide}'
},
modal: {

View File

@@ -40,6 +40,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'پچھلا',
next: 'اگلا',
dots: 'دکھانے کے لیے سلائیڈ منتخب کریں',
goto: 'سلائیڈ {slide} پر جائیں'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Ortga',
next: 'Oldinga',
dots: 'Koʻrsatish uchun slaydni tanlang',
goto: '{slide}-slaydga otish'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: 'Trước',
next: 'Sau',
dots: 'Chọn slide để hiển thị',
goto: 'Đi tới ô {slide}'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: '上一页',
next: '下一页',
dots: '选择要显示的幻灯片',
goto: '跳转到第 {slide} 页'
},
modal: {

View File

@@ -39,6 +39,7 @@ export default defineLocale<Messages>({
carousel: {
prev: '上一頁',
next: '下一頁',
dots: '選擇要顯示的投影片',
goto: '跳轉到第 {slide} 頁'
},
modal: {

View File

@@ -33,6 +33,7 @@ export type Messages = {
carousel: {
prev: string
next: string
dots: string
goto: string
}
modal: {

View File

@@ -61,17 +61,17 @@ exports[`Carousel > renders with dots correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ms-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<!--v-if-->
<div class="absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3"></div>
<div role="tablist" aria-label="Choose slide to display" class="absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3"></div>
</div>
</div>"
`;

View File

@@ -61,17 +61,17 @@ exports[`Carousel > renders with dots correctly 1`] = `
"<div role="region" aria-roledescription="carousel" tabindex="0" class="relative focus:outline-none">
<div class="overflow-hidden">
<div class="flex items-start flex-row -ms-4" style="transform: translate3d(NaNpx,0px,0px);">
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="group" aria-roledescription="slide" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=1" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=2" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=3" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=4" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=5" width="300" height="300" class="rounded-lg"></div>
<div role="tabpanel" class="min-w-0 shrink-0 basis-full ps-4"><img src="https://picsum.photos/600/600?random=6" width="300" height="300" class="rounded-lg"></div>
</div>
</div>
<div class="">
<!--v-if-->
<div class="absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3"></div>
<div role="tablist" aria-label="Choose slide to display" class="absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3"></div>
</div>
</div>"
`;