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