mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
3.94k stars 1.21k forks source link

[pickers][DatePicker v6.14.0] accessibility #12750

Open matchmakers69 opened 3 months ago

matchmakers69 commented 3 months ago

Steps to reproduce

https://codesandbox.io/p/sandbox/create-react-app-typescript-forked-2c4vqj?file=%2Fsrc%2Findex.tsx%3A23%2C69

Current behavior

My current implementation of MUI DatePicker looks like:

import { BaseTextFieldProps } from "@mui/material";
import { AdapterLuxon } from "@mui/x-date-pickers/AdapterLuxon";
import { DatePicker as MUIDatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { ReactComponent as CalendarIco } from "assets/icons/calendar-icon.svg";
import useValidationErrorMessage from "hooks/useValidationErrorMessage";
import { DateTime } from "luxon";
import { useController, useFormContext } from "react-hook-form";
import { palette } from "theme/palette";

type Views = "day" | "month" | "year";

type DatePickerProps = BaseTextFieldProps & {
  name: string;
  fullWidth?: boolean;
  inputFormat: string;
  hasPlaceholder?: boolean;
  value?: string | null;
  disablePast?: boolean;
  disableFuture?: boolean;
  label?: string;
  ariaRequired?: boolean;
  ["data-testid"]?: string;
  openTo?: Views;
};

const CalendarIcon = () => {
  return <CalendarIco data-testid="calendar-icon" />;
};

const DatePicker = ({
  name,
  inputFormat,
  fullWidth: propFullWidth,
  variant = "standard",
  className = "data-picker-date-field",
  hasPlaceholder = false,
  value = null,
  disablePast = false,
  disableFuture = false,
  ariaRequired,
  label = "",
  "data-testid": dataTestId = "date-picker",
  openTo = "day",
}: DatePickerProps) => {
  const {
    formState: { errors },
    clearErrors,
    control,
  } = useFormContext();

  const { field } = useController({ control, name, defaultValue: null });
  const errorMessage = useValidationErrorMessage(errors[name]?.message);

  return (
    <LocalizationProvider dateAdapter={AdapterLuxon}>
      <MUIDatePicker
        views={["day", "month", "year"]}
        openTo={openTo}
        data-testid={dataTestId}
        className={className}
        format={inputFormat}
        value={value !== null ? DateTime.fromFormat(value, "dd/MM/yyyy") : null}
        label={label}
        defaultValue={null}
        inputRef={field.ref}
        onChange={(date) => {
          clearErrors();
          field.onChange(date?.toFormat(inputFormat));
        }}
        slots={{
          openPickerIcon: CalendarIcon,
        }}
        slotProps={{
          popper: {
            className: "data-hj-suppress",
          },
          textField: {
            variant: variant,
            helperText: errorMessage,
            error: !!errors[name],
            fullWidth: propFullWidth,
            InputProps: {
              placeholder: hasPlaceholder ? inputFormat.toUpperCase() : "",
              "aria-required": ariaRequired,
            },
            sx: {
              "& input": {
                padding: "17px 0 9px 0",
              },
              "& .MuiInputBase-inputAdornedEnd": {
                padding: "17px 28px 9px 0",
              },
              "& fieldset": {
                borderColor: palette.border.light,
                color: palette.text.default,
              },
              "& svg": {
                display: "inline-block",
                width: 22,
                height: 22,
              },
            },
          },
          inputAdornment: {
            position: "end",
          },
        }}
        disablePast={disablePast}
        disableFuture={disableFuture}
      />
    </LocalizationProvider>
  );
};

export default DatePicker;

Expected behavior

Desktop screenreader

Mobile screenreader

Context

Basically I need to make my current implementation a bit more accessibility friendly

Your environment

npx @mui/envinfo ``` Don't forget to mention which browser you used. Output from `npx @mui/envinfo` goes here. ```

Search keywords: accessibity screen readers, MUI DatePicker

LukasTy commented 3 months ago

Hello @matchmakers69, thank you for creating this issue. 👍 Could you clarify the issue by providing the points that you think are missing from your list? If I understood correctly, you have already gone through your process of testing and noticed certain behaviors missing, so, it would be great having a better direction so that we could focus on what is missing.

One clear behavior that we are missing is the Shift+PageUp/PageDown to move through the years.

matchmakers69 commented 3 months ago

Great! , thanks for reply @LukasTy

These things dont work as they should:

  1. I want to understand what new date has been selected via a screenreader
  2. I want to understand what date exists in the field before I have amended it
  3. I want to be able understand navigation through months and years to find the desired date using a screenreader
  4. I want to be able to select my desired date using a keyboard
  5. I want to be notified if I have selected an invalid date via a screenreader

Just wondering if there would be a way or what i need to do to achieve that with my current implementation?

LukasTy commented 3 months ago

I'll start by saying that our behavior has been inspired by this example: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/.

These things dont work as they should:

  1. I want to understand what new date has been selected via a screenreader

Do you mean the dialog behavior here? I think that our behavior is nearly identical to WAI-ARIA example. Do you have ideas on how we could improve it? What would you expect to hear when changing days?

  1. I want to understand what date exists in the field before I have amended it

By "field" do you mean the input? If yes, then I suggest you read about the accessible DOM structure. What you are asking is practically impossible by using an input and going for the behavior we have now.

  1. I want to be able understand navigation through months and years to find the desired date using a screenreader

Do you refer to the dialog experience here? What do you feel missing in the months and years navigation? Is it the missing keyboard interaction I've referred to? Currently, the active month and year are announced when the month or year changes.

  1. I want to be able to select my desired date using a keyboard

Do you mean the input/field behavior here again? If yes, please refer to the answer to 2nd question.

  1. I want to be notified if I have selected an invalid date via a screenreader

Do you mean the field/input behavior here as well? In this case, NVDA does mention when the input is invalid for accessible DOM structure.

Just wondering if there would be a way or what i need to do to achieve that with my current implementation?

I'm unsure if that is doable with changes only in userland. The internal components will need certain changes.

github-actions[bot] commented 3 months ago

The issue has been inactive for 7 days and has been automatically closed.

github-actions[bot] commented 3 months ago

The issue has been inactive for 7 days and has been automatically closed.