Vertical Slider
diff --git a/docs/data/material/components/slider/VerticalAccessibleSlider.js b/docs/data/material/components/slider/VerticalAccessibleSlider.js
deleted file mode 100644
index e9892bcc838cdd..00000000000000
--- a/docs/data/material/components/slider/VerticalAccessibleSlider.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as React from 'react';
-import Box from '@mui/material/Box';
-import Slider from '@mui/material/Slider';
-
-export default function VerticalAccessibleSlider() {
- function preventHorizontalKeyboardNavigation(event) {
- if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
- event.preventDefault();
- }
- }
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/material/components/slider/VerticalAccessibleSlider.tsx b/docs/data/material/components/slider/VerticalAccessibleSlider.tsx
deleted file mode 100644
index bc66892e7f20b3..00000000000000
--- a/docs/data/material/components/slider/VerticalAccessibleSlider.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as React from 'react';
-import Box from '@mui/material/Box';
-import Slider from '@mui/material/Slider';
-
-export default function VerticalAccessibleSlider() {
- function preventHorizontalKeyboardNavigation(event: React.KeyboardEvent) {
- if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
- event.preventDefault();
- }
- }
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/material/components/slider/VerticalAccessibleSlider.tsx.preview b/docs/data/material/components/slider/VerticalAccessibleSlider.tsx.preview
deleted file mode 100644
index 2f23a0596d7eee..00000000000000
--- a/docs/data/material/components/slider/VerticalAccessibleSlider.tsx.preview
+++ /dev/null
@@ -1,12 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/material/components/slider/VerticalSlider.js b/docs/data/material/components/slider/VerticalSlider.js
index 502b71bdb8bb65..5ae94100d85d93 100644
--- a/docs/data/material/components/slider/VerticalSlider.js
+++ b/docs/data/material/components/slider/VerticalSlider.js
@@ -2,36 +2,13 @@ import * as React from 'react';
import Stack from '@mui/material/Stack';
import Slider from '@mui/material/Slider';
-function valuetext(value) {
- return `${value}°C`;
-}
-
-const marks = [
- {
- value: 0,
- label: '0°C',
- },
- {
- value: 20,
- label: '20°C',
- },
- {
- value: 37,
- label: '37°C',
- },
- {
- value: 100,
- label: '100°C',
- },
-];
-
export default function VerticalSlider() {
return (
@@ -45,7 +22,7 @@ export default function VerticalSlider() {
'Temperature'}
orientation="vertical"
- getAriaValueText={valuetext}
+ getAriaValueText={getAriaValueText}
defaultValue={[20, 37]}
valueLabelDisplay="auto"
marks={marks}
@@ -53,3 +30,26 @@ export default function VerticalSlider() {
);
}
+
+function getAriaValueText(value) {
+ return `${value}°C`;
+}
+
+const marks = [
+ {
+ value: 0,
+ label: '0°C',
+ },
+ {
+ value: 20,
+ label: '20°C',
+ },
+ {
+ value: 37,
+ label: '37°C',
+ },
+ {
+ value: 100,
+ label: '100°C',
+ },
+];
diff --git a/docs/data/material/components/slider/VerticalSlider.tsx b/docs/data/material/components/slider/VerticalSlider.tsx
index 2f4e4ffa167ca8..743b7103b07703 100644
--- a/docs/data/material/components/slider/VerticalSlider.tsx
+++ b/docs/data/material/components/slider/VerticalSlider.tsx
@@ -2,36 +2,13 @@ import * as React from 'react';
import Stack from '@mui/material/Stack';
import Slider from '@mui/material/Slider';
-function valuetext(value: number) {
- return `${value}°C`;
-}
-
-const marks = [
- {
- value: 0,
- label: '0°C',
- },
- {
- value: 20,
- label: '20°C',
- },
- {
- value: 37,
- label: '37°C',
- },
- {
- value: 100,
- label: '100°C',
- },
-];
-
export default function VerticalSlider() {
return (
@@ -45,7 +22,7 @@ export default function VerticalSlider() {
'Temperature'}
orientation="vertical"
- getAriaValueText={valuetext}
+ getAriaValueText={getAriaValueText}
defaultValue={[20, 37]}
valueLabelDisplay="auto"
marks={marks}
@@ -53,3 +30,26 @@ export default function VerticalSlider() {
);
}
+
+function getAriaValueText(value: number) {
+ return `${value}°C`;
+}
+
+const marks = [
+ {
+ value: 0,
+ label: '0°C',
+ },
+ {
+ value: 20,
+ label: '20°C',
+ },
+ {
+ value: 37,
+ label: '37°C',
+ },
+ {
+ value: 100,
+ label: '100°C',
+ },
+];
diff --git a/docs/data/material/components/slider/slider.md b/docs/data/material/components/slider/slider.md
index aa6afc620b1e17..e473d6375eb37b 100644
--- a/docs/data/material/components/slider/slider.md
+++ b/docs/data/material/components/slider/slider.md
@@ -98,17 +98,23 @@ You can learn more about this in the [overrides documentation page](/material-ui
## Vertical sliders
+Set the `orientation` prop to `"vertical"` to create vertical sliders. The thumb will track vertical movement instead of horizontal movement.
+
{{"demo": "VerticalSlider.js"}}
-**WARNING**: Chrome, Safari and newer Edge versions that is any browser based on WebKit exposes `` as horizontal ([chromium issue #40736841](https://issues.chromium.org/issues/40736841)).
-By applying `-webkit-appearance: slider-vertical;` the slider is exposed as vertical.
+:::warning
+Chrome versions below 124 implement `aria-orientation` incorrectly for vertical sliders and exposes them as `'horizontal'` in the accessibility tree. ([Chromium issue #40736841](https://issues.chromium.org/issues/40736841))
+
+The `-webkit-appearance: slider-vertical` CSS property can be used to correct this for these older versions, with the trade-off of causing a console warning in newer Chrome versions:
-However, by applying `-webkit-appearance: slider-vertical;` keyboard navigation for horizontal keys (Arrow Left, Arrow Right) is reversed ([chromium issue #40739626](https://issues.chromium.org/issues/40739626)).
-Usually, up and right should increase and left and down should decrease the value.
-If you apply `-webkit-appearance` you could prevent keyboard navigation for horizontal arrow keys for a truly vertical slider.
-This might be less confusing to users compared to a change in direction.
+```css
+.MuiSlider-thumb input {
+ -webkit-appearance: slider-vertical;
+}
+```
-{{"demo": "VerticalAccessibleSlider.js"}}
+For Chrome 124 and newer, the slider includes the CSS [`writing-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_writing_modes/Vertical_controls#range_sliders_meters_and_progress_bars) property that fixes this bug.
+:::
## Marks placement
diff --git a/packages/mui-material/src/Slider/Slider.test.js b/packages/mui-material/src/Slider/Slider.test.js
index 8b8d868434dbbd..8498815e1ff923 100644
--- a/packages/mui-material/src/Slider/Slider.test.js
+++ b/packages/mui-material/src/Slider/Slider.test.js
@@ -254,6 +254,50 @@ describe('', () => {
expect(handleChange.args[0][1]).to.equal(80);
expect(handleChange.args[1][1]).to.equal(78);
});
+
+ describe('vertical orientation', () => {
+ describe('keyboard interactions', () => {
+ it('ArrowLeft and ArrowDown decrements the value', () => {
+ const { getByRole } = render();
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '50');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowLeft' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '49');
+
+ fireEvent.keyDown(slider, { key: 'ArrowDown' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '48');
+ });
+
+ it('ArrowRight and ArrowUp increments the value', () => {
+ const { getByRole } = render();
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '50');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowRight' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '51');
+
+ fireEvent.keyDown(slider, { key: 'ArrowUp' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '52');
+ });
+ });
+ });
});
describe('range', () => {
@@ -421,41 +465,157 @@ describe('', () => {
});
describe('prop: step', () => {
- it('should handle a null step', () => {
- const { getByRole, container } = render(
- ,
- );
- stub(container.firstChild, 'getBoundingClientRect').callsFake(() => ({
- width: 100,
- height: 10,
- bottom: 10,
- left: 0,
- }));
- const slider = getByRole('slider');
+ describe('when step is `null`', () => {
+ it('values are defined by mark values', () => {
+ const { getByRole, container } = render(
+ ,
+ );
+ stub(container.firstChild, 'getBoundingClientRect').callsFake(() => ({
+ width: 100,
+ height: 10,
+ bottom: 10,
+ left: 0,
+ }));
+ const slider = getByRole('slider');
+
+ fireEvent.touchStart(
+ container.firstChild,
+ createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),
+ );
+ expect(slider).to.have.attribute('aria-valuenow', '20');
- fireEvent.touchStart(
- container.firstChild,
- createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),
- );
- expect(slider).to.have.attribute('aria-valuenow', '20');
+ fireEvent.change(slider, {
+ target: {
+ value: 21,
+ },
+ });
+ expect(slider).to.have.attribute('aria-valuenow', '30');
- fireEvent.change(slider, {
- target: {
- value: 21,
- },
+ fireEvent.change(slider, {
+ target: {
+ value: 29,
+ },
+ });
+ expect(slider).to.have.attribute('aria-valuenow', '20');
});
- expect(slider).to.have.attribute('aria-valuenow', '30');
- fireEvent.change(slider, {
- target: {
- value: 29,
- },
+ describe('keyboard interactions', () => {
+ describe('horizontal orientation', () => {
+ it('ArrowLeft and ArrowDown decrements the value', () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '79');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowLeft' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '29');
+
+ fireEvent.keyDown(slider, { key: 'ArrowDown' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '19');
+ });
+
+ it('ArrowRight and ArrowUp increments the value', () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '9');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowRight' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '19');
+
+ fireEvent.keyDown(slider, { key: 'ArrowUp' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '29');
+ });
+ });
+
+ describe('vertical orientation', () => {
+ it('ArrowLeft and ArrowDown decrements the value', () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '79');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowLeft' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '29');
+
+ fireEvent.keyDown(slider, { key: 'ArrowDown' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '19');
+ });
+
+ it('ArrowRight and ArrowUp increments the value', () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '9');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowRight' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '19');
+
+ fireEvent.keyDown(slider, { key: 'ArrowUp' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '29');
+ });
+ });
});
- expect(slider).to.have.attribute('aria-valuenow', '20');
});
it('change events with non integer numbers should work', () => {
@@ -986,6 +1146,112 @@ describe('', () => {
expect(handleChange.args[0][1]).to.equal(80);
expect(handleChange.args[1][1]).to.equal(78);
});
+
+ describe('keyboard interactions', () => {
+ it('ArrowRight and ArrowDown decrements the value', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '50');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowRight' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '49');
+
+ fireEvent.keyDown(slider, { key: 'ArrowDown' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '48');
+ });
+
+ it('ArrowLeft and ArrowUp increments the value', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '50');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'ArrowLeft' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '51');
+
+ fireEvent.keyDown(slider, { key: 'ArrowUp' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '52');
+ });
+
+ it('End key sets the value to max', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '50');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'End' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '99');
+ });
+
+ it('Home key sets the value to min', () => {
+ const { getByRole } = render(
+
+
+ ,
+ );
+
+ const slider = getByRole('slider');
+
+ expect(slider).to.have.attribute('aria-valuenow', '50');
+
+ act(() => {
+ slider.focus();
+ });
+
+ fireEvent.keyDown(slider, { key: 'Home' });
+
+ expect(slider).to.have.attribute('aria-valuenow', '1');
+ });
+ });
});
describe('warnings', () => {
diff --git a/packages/mui-material/src/Slider/useSlider.ts b/packages/mui-material/src/Slider/useSlider.ts
index 0cd7ba016c3387..7dc3b3b2b54b99 100644
--- a/packages/mui-material/src/Slider/useSlider.ts
+++ b/packages/mui-material/src/Slider/useSlider.ts
@@ -687,6 +687,11 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
};
};
+ let cssWritingMode: 'vertical-rl' | 'vertical-lr' | undefined;
+ if (orientation === 'vertical') {
+ cssWritingMode = isRtl ? 'vertical-rl' : 'vertical-lr';
+ }
+
const getHiddenInputProps = = {}>(
externalProps: ExternalProps = {} as ExternalProps,
): UseSliderHiddenInputProps => {
@@ -724,6 +729,7 @@ export function useSlider(parameters: UseSliderParameters): UseSliderReturnValue
// So that VoiceOver's focus indicator matches the thumb's dimensions
width: '100%',
height: '100%',
+ writingMode: cssWritingMode,
},
};
};
diff --git a/test/regressions/index.js b/test/regressions/index.js
index 8d377681dfb032..11618772af4aba 100644
--- a/test/regressions/index.js
+++ b/test/regressions/index.js
@@ -178,7 +178,6 @@ const blacklist = [
'docs-components-skeleton/Facebook.png', // Flaky image loading
'docs-components-skeleton/SkeletonChildren.png', // flaky image loading
'docs-components-skeleton/YouTube.png', // Flaky image loading
- 'docs-components-slider/VerticalAccessibleSlider.png', // Redundant
'docs-components-snackbars/ConsecutiveSnackbars.png', // Needs interaction
'docs-components-snackbars/CustomizedSnackbars.png', // Redundant
'docs-components-snackbars/DirectionSnackbar.png', // Needs interaction