Window Splitter
The WindowSplitter
component can be used with the useWindowSplitter
hook to
allow the user to resize windows or panels within the application using the
mouse or keyboard. The --rmd-window-splitter-position
custom property can be
used to set the height
or width
of panels and is used to set the
WindowSplitter
's position within the viewport or relative container.
If the entire layout should be resizable, see the Resizable Navigation Layout and useResizableLayout instead.
Horizontal Resizable Sheet Example
This example will show how to create a resizable Sheet
using the
WindowSplitter
. The useWindowSplitter
hook requires a min
and max
value
(in pixels) and provides the current value
(in pixels) which can be used to
set the width
or update custom properties.
The useWindowSize hook can be used to dynamically set
the max
value if the user resizes the window.
"use client";
import { Button } from "@react-md/core/button/Button";
import { DialogHeader } from "@react-md/core/dialog/DialogHeader";
import { DialogTitle } from "@react-md/core/dialog/DialogTitle";
import { Sheet } from "@react-md/core/sheet/Sheet";
import { useToggle } from "@react-md/core/useToggle";
import { useWindowSize } from "@react-md/core/useWindowSize";
import { WindowSplitter } from "@react-md/core/window-splitter/WindowSplitter";
import { useWindowSplitter } from "@react-md/core/window-splitter/useWindowSplitter";
import CloseIcon from "@react-md/material-icons/CloseIcon";
import { type ReactElement, useId } from "react";
export default function HorizontalResizableSheetExample(): ReactElement {
const sheetId = useId();
const titleId = useId();
const { enable: show, disable: hide, toggled: visible } = useToggle();
const { width } = useWindowSize({ disableHeight: true });
const { value, splitterProps } = useWindowSplitter({
min: 256,
max: Math.max(400, width - width / 4),
defaultValue: 256,
});
return (
<>
<Button onClick={show}>Show</Button>
<Sheet
aria-labelledby={titleId}
id={sheetId}
visible={visible}
onRequestClose={hide}
style={{
"--rmd-sheet-static-width": `${value}px`,
"--rmd-window-splitter-position": `${value}px`,
}}
>
<DialogHeader>
<DialogTitle id={titleId}>Resizable Sheet</DialogTitle>
<Button aria-label="Close" onClick={hide} buttonType="icon">
<CloseIcon />
</Button>
</DialogHeader>
<WindowSplitter
{...splitterProps}
aria-controls={sheetId}
aria-label="Resize Sheet"
/>
</Sheet>
</>
);
}
Reversed Horizontal Resizable Sheet Example
If the panel size should increment when dragging from right-to-left instead of
left-to-right, enable the reversed
option for the useWindowSplitter
hook.
The dragging behavior automatically updates for right-to-left
languages when using the WritingDirectionProvider
. Try changing the website
orientation through the options menu to see it in action.
"use client";
import { Button } from "@react-md/core/button/Button";
import { DialogHeader } from "@react-md/core/dialog/DialogHeader";
import { DialogTitle } from "@react-md/core/dialog/DialogTitle";
import { Sheet } from "@react-md/core/sheet/Sheet";
import { useToggle } from "@react-md/core/useToggle";
import { useWindowSize } from "@react-md/core/useWindowSize";
import { WindowSplitter } from "@react-md/core/window-splitter/WindowSplitter";
import { useWindowSplitter } from "@react-md/core/window-splitter/useWindowSplitter";
import CloseIcon from "@react-md/material-icons/CloseIcon";
import { type ReactElement, useId } from "react";
export default function ReversedHorizontalResizableSheetExample(): ReactElement {
const sheetId = useId();
const titleId = useId();
const { enable: show, disable: hide, toggled: visible } = useToggle();
const { width } = useWindowSize({ disableHeight: true });
const { value, splitterProps } = useWindowSplitter({
min: 256,
max: Math.max(400, width - width / 4),
reversed: true,
defaultValue: 256,
});
return (
<>
<Button onClick={show}>Show</Button>
<Sheet
aria-labelledby={titleId}
id={sheetId}
visible={visible}
onRequestClose={hide}
style={{
"--rmd-sheet-static-width": `${value}px`,
"--rmd-window-splitter-position": `${value}px`,
}}
position="right"
>
<DialogHeader>
<DialogTitle id={titleId}>Resizable Sheet</DialogTitle>
<Button aria-label="Close" onClick={hide} buttonType="icon">
<CloseIcon />
</Button>
</DialogHeader>
<WindowSplitter
{...splitterProps}
aria-controls={sheetId}
aria-label="Resize Sheet"
/>
</Sheet>
</>
);
}
Vertical Resizable Sheet Example
The WindowSplitter
and useWindowSplitter
can be used to resize vertically
as well. Just enable the vertical
option for the useWindowSplitter
.
"use client";
import { Button } from "@react-md/core/button/Button";
import { DialogHeader } from "@react-md/core/dialog/DialogHeader";
import { DialogTitle } from "@react-md/core/dialog/DialogTitle";
import { Sheet } from "@react-md/core/sheet/Sheet";
import { useToggle } from "@react-md/core/useToggle";
import { useWindowSize } from "@react-md/core/useWindowSize";
import { WindowSplitter } from "@react-md/core/window-splitter/WindowSplitter";
import { useWindowSplitter } from "@react-md/core/window-splitter/useWindowSplitter";
import CloseIcon from "@react-md/material-icons/CloseIcon";
import { type ReactElement, useId } from "react";
export default function VerticalResizableSheetExample(): ReactElement {
const sheetId = useId();
const titleId = useId();
const { enable: show, disable: hide, toggled: visible } = useToggle();
const { height } = useWindowSize({ disableWidth: true });
const { value, splitterProps } = useWindowSplitter({
min: 256,
// allow up to 3/4 of the window size
max: Math.max(400, height - height / 4),
// this normally defaults to `Math.ceil((max - min) / 2)`
defaultValue: 256,
vertical: true,
});
return (
<>
<Button onClick={show}>Show</Button>
<Sheet
aria-labelledby={titleId}
id={sheetId}
visible={visible}
onRequestClose={hide}
style={{
"--rmd-sheet-height": `${value}px`,
"--rmd-window-splitter-position": `${value}px`,
}}
position="top"
verticalSize="touch"
>
<DialogHeader>
<DialogTitle id={titleId}>Resizable Sheet</DialogTitle>
<Button aria-label="Close" onClick={hide} buttonType="icon">
<CloseIcon />
</Button>
</DialogHeader>
<WindowSplitter
{...splitterProps}
aria-controls={sheetId}
aria-label="Resize Sheet"
/>
</Sheet>
</>
);
}
Reversed Vertical Resizable Sheet Example
If the panel size should increment when dragging from down-to-up instead of
up-to-down, enable the reversed
option for the useWindowSplitter
hook.
"use client";
import { Button } from "@react-md/core/button/Button";
import { DialogHeader } from "@react-md/core/dialog/DialogHeader";
import { DialogTitle } from "@react-md/core/dialog/DialogTitle";
import { Sheet } from "@react-md/core/sheet/Sheet";
import { useToggle } from "@react-md/core/useToggle";
import { useWindowSize } from "@react-md/core/useWindowSize";
import { WindowSplitter } from "@react-md/core/window-splitter/WindowSplitter";
import { useWindowSplitter } from "@react-md/core/window-splitter/useWindowSplitter";
import CloseIcon from "@react-md/material-icons/CloseIcon";
import { type ReactElement, useId } from "react";
export default function ReversedVerticalResizableSheetExample(): ReactElement {
const sheetId = useId();
const titleId = useId();
const { enable: show, disable: hide, toggled: visible } = useToggle();
const { height } = useWindowSize({ disableWidth: true });
const { value, splitterProps } = useWindowSplitter({
min: 256,
// allow up to 3/4 of the window size
max: Math.max(400, height - height / 4),
// this normally defaults to `Math.ceil((max - min) / 2)`
defaultValue: 256,
vertical: true,
reversed: true,
});
return (
<>
<Button onClick={show}>Show</Button>
<Sheet
aria-labelledby={titleId}
id={sheetId}
visible={visible}
onRequestClose={hide}
style={{
"--rmd-sheet-height": `${value}px`,
"--rmd-window-splitter-position": `${value}px`,
}}
position="bottom"
verticalSize="touch"
>
<DialogHeader>
<DialogTitle id={titleId}>Resizable Sheet</DialogTitle>
<Button aria-label="Close" onClick={hide} buttonType="icon">
<CloseIcon />
</Button>
</DialogHeader>
<WindowSplitter
{...splitterProps}
aria-controls={sheetId}
aria-label="Resize Sheet"
/>
</Sheet>
</>
);
}
Relative Horizontal Example
The WindowSplitter
defaults to using position: fixed
but can also be used
within position: relative
containers by enabling the disableFixed
prop. The
max value will also need to be updated to be based on the container size which
can be done using the useElementSize hook or another implementation.
Relative Vertical Example
This is the same as above, but just vertical instead of horizontal.
"use client";
import { Box } from "@react-md/core/box/Box";
import { Card } from "@react-md/core/card/Card";
import { CardContent } from "@react-md/core/card/CardContent";
import { WindowSplitter } from "@react-md/core/window-splitter/WindowSplitter";
import { useWindowSplitter } from "@react-md/core/window-splitter/useWindowSplitter";
import { type ReactElement, useId } from "react";
export default function RelativeVerticalExample(): ReactElement {
const panelId = useId();
const { value, splitterProps } = useWindowSplitter({
min: 52,
max: 800,
vertical: true,
defaultValue: 52,
});
return (
<Box
stacked
style={{
"--rmd-window-splitter-position": `${value}px`,
position: "relative",
}}
fullWidth
disablePadding
>
<Card id={panelId} fullWidth style={{ height: value }}>
<CardContent>Main Panel</CardContent>
</Card>
<WindowSplitter
aria-controls={panelId}
aria-labelledby={panelId}
{...splitterProps}
disableFixed
/>
<Card fullWidth>
<CardContent>Secondary Panel</CardContent>
</Card>
</Box>
);
}
Accessibility
The window splitter implements the window
splitter
specifications and will require an aria-label
/aria-labelledby
.
Keyboard Behavior
- The Home and End keys will move the splitter to the largest and smallest allowed size for both horizontal and vertical window splitters
- The ArrowLeft and ArrowRight keys can be used to decrement and increment the horizontal window splitter size
- The ArrowUp and ArrowDown keys can be used to decrement and increment the vertical window splitter size