SidePanel

Beta

SidePanels are vertical containers used to display additional information that supports the main content area or to edit specific content within the page.

installyarn add @clayui/core
versionNPM Version
useimport {SidePanel} from '@clayui/core';

Example

import {SidePanel, Provider} from '@clayui/core';
import Button from '@clayui/button';
import React from 'react';

import '@clayui/css/lib/css/atlas.css';

export default function App() {
	const [open, setOpen] = React.useState(false);

	const ref = React.useRef();

	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4" ref={ref} style={{minHeight: '100vh'}}>
				<Button
					aria-controls="sidepanel-example"
					aria-pressed={open}
					onClick={() => setOpen(!open)}
				>
					Open
				</Button>

				<SidePanel
					containerRef={ref}
					id="sidepanel-example"
					onOpenChange={setOpen}
					open={open}
				>
					<SidePanel.Header>
						<SidePanel.Title>Title</SidePanel.Title>
					</SidePanel.Header>
					<SidePanel.Body>Body</SidePanel.Body>
					<SidePanel.Footer>
						<Button.Group spaced>
							<Button>Primary</Button>
							<Button displayType="secondary">Secondary</Button>
						</Button.Group>
					</SidePanel.Footer>
				</SidePanel>
			</div>
		</Provider>
	);
}

Introduction

The SidePanel component provides a more easy and quick way for the users to consult or edit information without the need for deeper navigation.

SidePanels can anchor to the left or right side of the screen. They can be collapsible or triggered by a button or a link. When activated, the SidePanel opens and the content of the page is readjusted. You can navigate through the SidePanel content using both mouse and keyboard.

Anatomy

<SidePanel>
	<SidePanel.Header>
		<SidePanel.Title />
	</SidePanel.Header>
	<SidePanel.Body />
	<SidePanel.Footer />
</SidePanel>;

Content

The SidePanel component follows a compositional API with subcomponents: Header, Title, Body, and Footer. Refer to the design specifications to configure each part according to your use case.

he <SidePanel.Header /> subcomponent always renders the close button by default but leaves the left side of the header open for developer customization.

The <SidePanel.Title /> must always be used as a direct child of Header. It automatically configures accessibility attributes—such as setting the appropriate aria-labelledby relationship between the title and the panel.

<SidePanel.Header>
	<SidePanel.Title>Title</SidePanel.Title>
</SidePanel.Header>

The <SidePanel.Body /> and <SidePanel.Footer /> components are flexible containers that can render any content. However, you should follow design system guidelines and recommendations to ensure consistent layout and behavior across the application.

Open panel

The SidePanel component requires the open state to be controlled, even though it defaults to an uncontrolled behavior.

Opening the SidePanel can be triggered from any button or link in the DOM, so it’s your responsibility to ensure the trigger element meets accessibility standards. This includes adding appropriate attributes such as aria-controls and aria-pressed to the button:

import {SidePanel} from '@clayui/core';
import Button from '@clayui/button';

export default function App() {
	const [open, setOpen] = useState(false);

	const ref = useRef();

	return (
		<>
			<Button
				aria-controls="sidepanel-example"
				aria-pressed={open}
				onClick={() => setOpen(!open)}
			>
				Open
			</Button>

			<div ref={ref}>
				<SidePanel
					containerRef={ref}
					id="sidepanel-example"
					onOpenChange={setOpen}
					open={open}
				/>
			</div>
		</>
	);
}

Make sure the id used in aria-controls matches the id of the <SidePanel /> component to maintain a proper relationship between the trigger and the panel.

Direction

The SidePanel can be displayed on the right or left side by setting the direction property.

<SidePanel direction="left" />

Position

The SidePanel is designed to use either absolute or fixed positioning.

absolute positioning is the default which allows the SidePanel to be used in more flexible contexts If the SidePanel cannot be placed at the edge of the window then absolute positioning should be used. When the SidePanel is positioned absolutely the header and footer may be scrolled out of view on longer pages.

fixed positioning should only be used when the SidePanel can be placed at a horizontal edge of the window without any elements above it that are not also using fixed or sticky positioning. With fixed positioning the header and footer will also be fixed and the SidePanel body will have it’s own independent scroll.

<SidePanel position="fixed" />

Variants

Drilldown

This variant provides a Drilldown in the Side Panel, allowing users to explore more detailed content without losing their navigation context. It facilitates the visualization of hierarchical or related information within the side panel.

import Button from '@clayui/button';
import {Provider, SidePanel, SidePanelWithDrilldown} from '@clayui/core';
import React, {useRef, useState} from 'react';

import '@clayui/css/lib/css/atlas.css';

export default function App() {
	const [open, setOpen] = React.useState(true);
	const [panelKey, setPanelKey] = useState('x1');

	const ref = React.useRef();

	return (
		<Provider spritemap="/public/icons.svg">
			<div className="p-4" ref={ref} style={{minHeight: '100vh'}}>
				<Button
					aria-controls="sidepanel-example"
					aria-pressed={open}
					onClick={() => setOpen(!open)}
				>
					Open
				</Button>
				<SidePanelWithDrilldown
					containerRef={ref}
					id="sidepanel-example"
					onOpenChange={setOpen}
					onSelectedPanelKeyChange={setPanelKey}
					open={open}
					panels={{
						x1: {
							component: (
								<SidePanel.Body>
									<button
										className="list-group-item list-group-item-action"
										onClick={() => setPanelKey('x2')}
									>
										List Item 1
									</button>
									<button
										className="list-group-item list-group-item-action"
										onClick={() => setPanelKey('x3')}
									>
										List Item 2
									</button>
								</SidePanel.Body>
							),
							title: 'Drilldown Title',
						},
						x2: {
							component: (
								<SidePanel.Body>
									This is more detailed content without losing
									the navigation context.
								</SidePanel.Body>
							),
							headerMessages: {
								backAriaLabel: 'Back to previous panel',
							},
							parentKey: 'x1',
							title: 'List Item 1 Detail',
						},
						x3: {
							component: (
								<SidePanel.Body>
									This is more detailed content without losing
									the navigation context.
								</SidePanel.Body>
							),
							headerMessages: {
								backAriaLabel: 'Back to previous panel',
							},
							parentKey: 'x1',
							title: 'List Item 2 Detail',
						},
					}}
					position="fixed"
					selectedPanelKey={panelKey}
				/>
			</div>
		</Provider>
	);
}

The Drilldown component establishes navigation by referencing panels through their keys, so each panel must have a unique key.

const panels = {
	of23: {
		component: <SidePanel.Body>Drilldown Body</SidePanel.Body>,
		title: 'Drilldown Title',
	},
};

Using the key, you can link a panel to its parent by setting its parentKey property.

const panels = {
	of23: {
		component: (
			<SidePanel.Body>
				Drilldown Body
				<button onClick={() => setPanelKey('of09')}>List Item</button>
			</SidePanel.Body>
		),
		title: 'Drilldown Title',
	},
	of09: {
		component: (
			<SidePanel.Body>
				This is more detailed content without losing the navigation
				context.
			</SidePanel.Body>
		),
		title: 'List Item Detail',
		parentKey: 'of23',
	},
};
Info The SidePanelWithDrilldown component always displays a header. If a panel defines a parentKey (i.e., it is part of a hierarchy), the header includes a back button that lets users return to the parent panel.
An important thing to keep in mind is that the SidePanelWithDrilldown component renders panels in the order in which they are defined. If the panels are defined in the wrong order, the panel animation may not behave correctly.

API Reference

SidePanel

typeof SidePanel
Parameters

{ 'aria-describedby'?: string; 'aria-label'?: string; 'aria-labelledby'?: string; as?: "aside" | "div" | "nav" | "section"; className?: string; children: React.ReactNode; containerRef: React.RefObject<HTMLElement>; defaultOpen?: boolean; direction?: "left" | "right"; displayType?: "light" | "dark"; externalSidePanelRef?: React.RefObject<HTMLDivElement>; id?: string; position?: "absolute" | "fixed"; triggerRef?: React.RefObject<HTMLElement>; }

aria-describedby

string | undefined

The global aria-describedby attribute identifies the element that describes the component.

aria-label

string | undefined

The aria-label attribute defines a string value that labels an interactive element.

aria-labelledby

string | undefined

The aria-labelledby attribute identifies the element (or elements) that labels the element it is applied to.

as

"nav" | "aside" | "div" | "section" | undefined

Custom component. aside - Secondary menus or filters, complementary information... (e.g product filters in an e-commerce site, related articles in a blog...). nav or section - If the ontent is essential for navigation, sidebar contains primary actions like important forms or sidebar is the only important content.

className

string | undefined

Sets the CSS className for the component.

children *

React.ReactNode

Children content to render a content.

containerRef *

React.RefObject<HTMLElement>

Element reference to the container of the SidePanel and primary content. NOTE: The containerRef is needed to properly handle layout and transitions of the SidePanel.

defaultOpen

boolean | undefined

Property to set the default value (uncontrolled).

direction

"left" | "right" | undefined

Direction the panel will render.

displayType

"light" | "dark" | undefined

Flag to determine which style the SidePanel will display.

externalSidePanelRef

React.RefObject<HTMLDivElement> | undefined

External reference for the side panel.

id

string | undefined

The id of the component.

position

"fixed" | "absolute" | undefined

Property to determine how the SidePanel will be positioned.

triggerRef

React.RefObject<HTMLElement> | undefined

Element reference to open the SidePanel. NOTE: SidePanel automatically identifies the trigger but it may not work in some cases when the element disappears from the DOM.

UncontrolledState | ControlledState
Returns
Element

SidePanelWithDrilldown

({ onSelectedPanelKeyChange, panels, selectedPanelKey, ...otherProps }: Props) => JSX.Element
Parameters

onSelectedPanelKeyChange *

InternalDispatch<string>

Callback is called when the selectedPanelKey prop changes (controlled).

panels *

Panels

Group of panels to be rendered

[key: React.Key]: Panel;

selectedPanelKey *

keyof Panels

Selected panel key

className

string | undefined

Sets the CSS className for the component.

aria-label

string | undefined

The aria-label attribute defines a string value that labels an interactive element.

open

boolean | undefined

id

string | undefined

The id of the component.

aria-describedby

string | undefined

The global aria-describedby attribute identifies the element that describes the component.

aria-labelledby

string | undefined

The aria-labelledby attribute identifies the element (or elements) that labels the element it is applied to.

defaultOpen

boolean | undefined

Property to set the default value (uncontrolled).

as

"nav" | "aside" | "div" | "section" | undefined

Custom component. aside - Secondary menus or filters, complementary information... (e.g product filters in an e-commerce site, related articles in a blog...). nav or section - If the ontent is essential for navigation, sidebar contains primary actions like important forms or sidebar is the only important content.

containerRef *

React.RefObject<HTMLElement>

Element reference to the container of the SidePanel and primary content. NOTE: The containerRef is needed to properly handle layout and transitions of the SidePanel.

direction

"left" | "right" | undefined

Direction the panel will render.

displayType

"light" | "dark" | undefined

Flag to determine which style the SidePanel will display.

externalSidePanelRef

React.RefObject<HTMLDivElement> | undefined

External reference for the side panel.

position

"fixed" | "absolute" | undefined

Property to determine how the SidePanel will be positioned.

triggerRef

React.RefObject<HTMLElement> | undefined

Element reference to open the SidePanel. NOTE: SidePanel automatically identifies the trigger but it may not work in some cases when the element disappears from the DOM.

onOpenChange

((open: boolean) => void) | ((open: boolean) => void) | undefined
Returns
Element
({ children, className, messages, sticky, onBack, }: Props) => JSX.Element
Parameters
Properties

children *

React.ReactNode

Children content to render a content.

className

string | undefined

Sets the CSS className for the component.

messages

Messages | undefined= {"backAriaLabel":"Go back.","closeAriaLabel":"Close the side panel."}

Messages for the Side Panel Header.

sticky

boolean | undefined

Property to make the Header sticky. Absolutely positioned SidePanel's should have the sidebar-header top CSS property adjusted to account for any fixed or sticky navigation bars on the page.

onBack

(() => void) | undefined

Function to execute when the back button is pressed.

Returns
Element

Title

({ children }: React.HTMLAttributes<HTMLDivElement>) => JSX.Element
Returns
Element

Body

({ children }: Props) => JSX.Element
Parameters
Properties

children *

React.ReactNode

Children content to render a content.

Returns
Element
({ children, className, sticky }: Props) => JSX.Element
Parameters
Properties

children *

React.ReactNode

Children content to render a content.

className

string | undefined

Sets the CSS className for the component.

sticky

boolean | undefined

Property to make the Footer sticky. Absolutely positioned SidePanel's should have the sidebar-footer bottom CSS property adjusted to account for any fixed or sticky footers on the page.

Returns
Element