(frontend) improve modal a11y: structure, labels, and title

added aria-label, structured text in p, and added title for better accessibility

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-09-08 11:58:06 +02:00
parent 9f9fae96e5
commit 8a310d004b
16 changed files with 114 additions and 39 deletions

View File

@@ -463,12 +463,14 @@ test.describe('Doc Editor', () => {
await expect(
page.getByRole('button', {
name: 'Download',
exact: true,
}),
).toBeVisible();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();

View File

@@ -38,7 +38,9 @@ test.describe('Doc Export', () => {
).toBeVisible();
await expect(page.getByRole('combobox', { name: 'Format' })).toBeVisible();
await expect(
page.getByRole('button', { name: 'Close the modal' }),
page.getByRole('button', {
name: 'Close the download modal',
}),
).toBeVisible();
await expect(page.getByTestId('doc-export-download-button')).toBeVisible();
});

View File

@@ -149,7 +149,7 @@ test.describe('Document grid item options', () => {
await page
.getByRole('button', {
name: 'Confirm deletion',
name: 'Delete document',
})
.click();

View File

@@ -100,7 +100,7 @@ test.describe('Doc Header', () => {
await page
.getByRole('button', {
name: 'Confirm deletion',
name: 'Delete document',
})
.click();

View File

@@ -33,7 +33,7 @@ test.describe('Document search', () => {
).toBeVisible();
await expect(
page.getByLabel('Search modal').getByText('search'),
page.getByRole('heading', { name: 'Search docs' }),
).toBeVisible();
const inputSearch = page.getByPlaceholder('Type the name of a document');
@@ -79,7 +79,7 @@ test.describe('Document search', () => {
await page.keyboard.press('Control+k');
await expect(
page.getByLabel('Search modal').getByText('search'),
page.getByRole('heading', { name: 'Search docs' }),
).toBeVisible();
await page.keyboard.press('Escape');

View File

@@ -30,15 +30,23 @@ export const AlertModal = ({
isOpen={isOpen}
size={ModalSize.MEDIUM}
onClose={onClose}
aria-describedby="alert-modal-title"
title={
<Text $size="h6" $align="flex-start" $variation="1000">
<Text
$size="h6"
as="h1"
$margin="0"
id="alert-modal-title"
$align="flex-start"
$variation="1000"
>
{title}
</Text>
}
rightActions={
<>
<Button
aria-label={t('Close the modal')}
aria-label={`${t('Cancel')} - ${title}`}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -55,12 +63,11 @@ export const AlertModal = ({
</>
}
>
<Box
aria-label={t('Confirmation button')}
className="--docs--alert-modal"
>
<Box className="--docs--alert-modal">
<Box>
<Text $variation="600">{description}</Text>
<Text $variation="600" as="p">
{description}
</Text>
</Box>
</Box>
</Modal>

View File

@@ -19,10 +19,11 @@ export const ModalConfirmDownloadUnsafe = ({
isOpen
closeOnClickOutside
onClose={() => onClose()}
aria-describedby="modal-confirm-download-unsafe-title"
rightActions={
<>
<Button
aria-label={t('Close the modal')}
aria-label={t('Cancel the download')}
color="secondary"
onClick={() => onClose()}
>
@@ -31,6 +32,7 @@ export const ModalConfirmDownloadUnsafe = ({
<Button
aria-label={t('Download')}
color="danger"
data-testid="modal-download-unsafe-button"
onClick={() => {
if (onConfirm) {
void onConfirm();
@@ -45,6 +47,8 @@ export const ModalConfirmDownloadUnsafe = ({
size={ModalSize.SMALL}
title={
<Text
as="h1"
id="modal-confirm-download-unsafe-title"
$gap="0.7rem"
$size="h6"
$align="flex-start"

View File

@@ -133,10 +133,11 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
closeOnClickOutside
onClose={() => onClose()}
hideCloseButton
aria-describedby="modal-export-title"
rightActions={
<>
<Button
aria-label={t('Close the modal')}
aria-label={t('Cancel the download')}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -165,6 +166,9 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
$width="100%"
>
<Text
as="h1"
$margin="0"
id="modal-export-title"
$size="h6"
$variation="1000"
$align="flex-start"
@@ -186,7 +190,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
$gap="1rem"
className="--docs--modal-export-content"
>
<Text $variation="600" $size="sm">
<Text $variation="600" $size="sm" as="p">
{t('Download your document in a .docx or .pdf format.')}
</Text>
<Select

View File

@@ -56,10 +56,11 @@ export const ModalRemoveDoc = ({
closeOnClickOutside
hideCloseButton
onClose={() => onClose()}
aria-describedby="modal-remove-doc-title"
rightActions={
<>
<Button
aria-label={t('Close the delete modal')}
aria-label={t('Cancel the deletion')}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -67,7 +68,7 @@ export const ModalRemoveDoc = ({
{t('Cancel')}
</Button>
<Button
aria-label={t('Confirm deletion')}
aria-label={t('Delete document')}
color="danger"
fullWidth
onClick={() =>
@@ -90,8 +91,9 @@ export const ModalRemoveDoc = ({
>
<Text
$size="h6"
as="h6"
$margin={{ all: '0' }}
as="h1"
id="modal-remove-doc-title"
$margin="0"
$align="flex-start"
$variation="1000"
>
@@ -104,12 +106,9 @@ export const ModalRemoveDoc = ({
</Box>
}
>
<Box
aria-label={t('Content modal to delete document')}
className="--docs--modal-remove-doc"
>
<Box className="--docs--modal-remove-doc">
{!isError && (
<Text $size="sm" $variation="600" $display="inline-block">
<Text $size="sm" $variation="600" $display="inline-block" as="p">
<Trans t={t}>
This document and <strong>any sub-documents</strong> will be
permanently deleted. This action is irreversible.

View File

@@ -5,7 +5,7 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDebouncedCallback } from 'use-debounce';
import { Box } from '@/components';
import { Box, Text } from '@/components';
import ButtonCloseModal from '@/components/modal/ButtonCloseModal';
import { QuickSearch } from '@/components/quick-search';
import { Doc, useDocUtils } from '@/docs/doc-management';
@@ -65,6 +65,7 @@ const DocSearchModalGlobal = ({
closeOnClickOutside
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
hideCloseButton
aria-describedby="doc-search-modal-title"
>
<Box
aria-label={t('Search modal')}
@@ -72,6 +73,14 @@ const DocSearchModalGlobal = ({
$justify="space-between"
className="--docs--doc-search-modal"
>
<Text
as="h1"
$margin="0"
id="doc-search-modal-title"
className="sr-only"
>
{t('Search docs')}
</Text>
<Box $position="absolute" $css="top: 12px; right: 12px;">
<ButtonCloseModal
aria-label={t('Close the search modal')}

View File

@@ -135,12 +135,20 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
isOpen
closeOnClickOutside
data-testid="doc-share-modal"
aria-label={t('Share modal')}
aria-describedby="doc-share-modal-title"
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
onClose={onClose}
title={
<Box $direction="row" $justify="space-between" $align="center">
<Box $align="flex-start">{t('Share the document')}</Box>
<Text
as="h1"
id="doc-share-modal-title"
$align="flex-start"
$size="small"
$weight="600"
>
{t('Share the document')}
</Text>
<ButtonCloseModal
aria-label={t('Close the share modal')}
onClick={onClose}
@@ -199,6 +207,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
$textAlign="center"
$variation="600"
$size="sm"
as="p"
>
{t(
'You can view this document but need additional access to see its members or modify settings.',

View File

@@ -69,10 +69,11 @@ export const ModalConfirmationVersion = ({
isOpen
closeOnClickOutside
onClose={() => onClose()}
aria-describedby="modal-confirmation-version-title"
rightActions={
<>
<Button
aria-label={t('Close the modal')}
aria-label={`${t('Cancel')} - ${t('Warning')}`}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -102,20 +103,24 @@ export const ModalConfirmationVersion = ({
}
size={ModalSize.SMALL}
title={
<Text $size="h6" $align="flex-start" $variation="1000">
<Text
as="h1"
$margin="0"
id="modal-confirmation-version-title"
$size="h6"
$align="flex-start"
$variation="1000"
>
{t('Warning')}
</Text>
}
>
<Box
aria-label={t('Modal confirmation to restore the version')}
className="--docs--modal-confirmation-version"
>
<Box className="--docs--modal-confirmation-version">
<Box>
<Text $variation="600">
<Text $variation="600" as="p">
{t('Your current document will revert to this version.')}
</Text>
<Text $variation="600">
<Text $variation="600" as="p">
{t('If a member is editing, his works can be lost.')}
</Text>
</Box>

View File

@@ -48,6 +48,7 @@ export const ModalSelectVersion = ({
closeOnClickOutside={true}
size={ModalSize.EXTRA_LARGE}
onClose={onClose}
aria-describedby="modal-select-version-title"
>
<NoPaddingStyle />
<Box
@@ -58,6 +59,14 @@ export const ModalSelectVersion = ({
$maxHeight="calc(100vh - 2em - 12px)"
$overflow="hidden"
>
<Text
as="h1"
$margin="0"
id="modal-select-version-title"
className="sr-only"
>
{t('Version history')}
</Text>
<Box
$css={css`
display: flex;

View File

@@ -272,6 +272,7 @@
"Close the search modal": "Such-Modal schließen",
"Close the share modal": "Freigabe-Modal schließen",
"Close the version history modal": "Versionsverlauf-Modal schließen",
"Close the download modal": "Download-Modal schließen",
"Collaborate": "Zusammenarbeiten",
"Collaborate and write in real time, without layout constraints.": "In Echtzeit und ohne Layout-Beschränkungen schreiben und zusammenarbeiten.",
"Collaborative writing, Simplified.": "Kollaboratives Schreiben, vereinfacht.",
@@ -463,6 +464,9 @@
"Close the search modal": "Close the search modal",
"Close the share modal": "Close the share modal",
"Close the version history modal": "Close the version history modal",
"Close the download modal": "Close the download modal",
"Cancel the deletion": "Cancel the deletion",
"Cancel the download": "Cancel the download",
"Open actions menu for document: {{title}}": "Open actions menu for document: {{title}}",
"Open the menu of actions for the document: {{title}}": "Open the menu of actions for the document: {{title}}",
"Share with {{count}} users_one": "Share with {{count}} user",
@@ -505,10 +509,13 @@
"Close the search modal": "Cerrar modal de búsqueda",
"Close the share modal": "Cerrar modal de compartir",
"Close the version history modal": "Cerrar modal de historial de versiones",
"Close the download modal": "Cerrar modal de descarga",
"Collaborate": "Colabora",
"Collaborate and write in real time, without layout constraints.": "Colaborar y escribir en tiempo real, sin restricciones de diseño.",
"Collaborative writing, Simplified.": "Escritura colaborativa, más sencilla.",
"Confirm deletion": "Confirmar borrado",
"Cancel the deletion": "Cancelar la eliminación",
"Cancel the download": "Cancelar la descarga",
"Connected": "Conectado",
"Content modal to delete document": "Modal para eliminar el documento",
"Content modal to export the document": "Ventana emergente para exportar el documento",
@@ -704,6 +711,8 @@
"Collaborative writing, Simplified.": "L'écriture collaborative simplifiée.",
"Confirm": "Confirmez",
"Confirm deletion": "Confirmer la suppression",
"Cancel the deletion": "Annuler la suppression",
"Cancel the download": "Annuler le téléchargement",
"Confirmation button": "Bouton de confirmation",
"Connected": "Connecté",
"Content modal to delete document": "Contenu modal pour supprimer le document",
@@ -1088,10 +1097,13 @@
"Close the search modal": "Sluit het zoek venster",
"Close the share modal": "Sluit het delen venster",
"Close the version history modal": "Sluit het versiehistorie venster",
"Close the download modal": "Sluit het download venster",
"Collaborate": "Samenwerken",
"Collaborate and write in real time, without layout constraints.": "Samenwerken en schrijven in realtime, zonder lay-out beperkingen.",
"Collaborative writing, Simplified.": "Vereenvoudigd samenwerkend",
"Confirm deletion": "Bevestig verwijdering",
"Cancel the deletion": "Annuleer de verwijdering",
"Cancel the download": "Annuleer de download",
"Connected": "Verbonden",
"Content modal to delete document": "Content venster om het document te verwijderen",
"Content modal to export the document": "Content venster om document te exporteren",

View File

@@ -82,3 +82,16 @@ main ::-webkit-scrollbar-thumb:hover,
nextjs-portal {
display: none;
}
/* Screen reader only - visually hidden but accessible to screen readers */
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip-path: inset(50%) !important;
white-space: nowrap !important;
border: 0 !important;
}