Skip to Content

Side Panel

Side Panels are containers that anchor to the left or right side of the screen.

Interaction Modes

Configuring

Editing

Navigating

Reviewing

Install

yarn add @workday/canvas-kit-react

Component Type

Container

Platform

Web

Component

Sana Canvas

Delivery Channels

Web

Version

16.1.7

Experience Surfaces

Page Body Inline

Component Type

Container

Platform

Web

Component

Sana Canvas

Delivery Channels

Web

Version

16.1.7

Experience Surfaces

Page Body Inline

Side Panel (Main) vs. Side Panel (Preview)

We recommend you use the Side Panel in the Main package (@workday/canvas-kit-react) documented here on this page. The Side Panel in the Preview package (@workday/canvas-kit-preview-react) will eventually be removed.

Anatomy

Image of a Side Panel in its expanded state with a Collapse Icon Button.
  1. Tooltip (Required if using Expand/Collapse Button): Tooltip used to provide additional visual affordance for the Expand/Collapse Button.
  2. Expand/Collapse Button (Optional): Icon only Tertiary Button variant used to open or close the Side Panel.
  3. Container: Rectangular container that houses the contents of the Side Panel. The container spans the full height of the viewport and are flush to the left or right edge of the screen.

Usage Guidance

  • Side Panels can either push and resize content as it expands within a page or float over page content. See the Expandable pattern for more detailed information on horizontal animation.
  • When the content of the Side Panel exceeds the height of the viewport, overflow behavior such as a scrollbar is introduced.
  • Consider the behavior of Side Panels at different responsive breakpoints and in different use cases. In use cases where the Side Panel is used to edit content within the page, keeping the Side Panel open and resizing the page content may be ideal. For use cases where a Side Panel is not required to remain open, enabling a Side Panel to automatically collapse when it reaches smaller screen sizes will prevent the panel from taking up too much of the screen until the user wants to take action on it.
  • When using the Expand/Collapse Button within the Side Panel, use a Tooltip to provide additional affordance that the icon is interactive and to improve accessibility for the Side Panel. When the Side Panel is expanded, tooltip text reads “Collapse” and when collapsed, the tooltip reads “Expand.”

When to Use

Although the elements within a Side Panel are highly configurable to support various use cases, they are commonly used in the following ways:

Local Page Navigation

Low-fidelity illustration of a Side Panel fixed to the left of the screen. The Panel contains an Icon Button used to collapse the Panel.
  • Provides users with a way to navigate within an area of your product.
  • Typically tied to the main content region.
  • Often collapsible but not closeable, meaning the Panel remains on the page and cannot be dismissed.

Editing and Displaying Additional Information

Low-fidelity illustration of a Side Panel fixed to the right of the screen. The Panel contains input fields.
  • Ideal for editing specific content within the page or displaying additional information that supports the main content area.
  • Can be temporary, meaning the Panel may disappear when the associated content on the main page is no longer in focus.

Panel Overlays

Low-fidelity illustration of a left Side Panel fixed to the right of the screen on top of an Overlay. The Panel contains a close button.
Low-fidelity illustration of a Side Panel fixed to the right of the screen on top of an Overlay. The Panel contains a close button and input fields.
  • When Panels open over an overlay, the user cannot interact with the main page. The overlay helps users focus attention on the contents of the Panel, making it ideal for higher-level navigation and editing or displaying additional information while minimizing distractions.
  • A Side Panel that opens over an overlay has a close Button but not a collapse. Activating the Button closes the Panel and the Overlay so the user can return focus to the main page.

Do’s and Don’ts

A page layout with a left navigation Side Panel and a right form Side Panel both open at once, leaving a narrow center content area.

Caution

Consider screen real estate when multiple Side Panels are present within a page. When multiple Side Panels are open at the same time, it may be overwhelming to users as their page content shrinks.

Two Side Panels side by side, one expanded with its icon-only button showing a "Collapse" Tooltip, the other collapsed showing an "Expand" Tooltip.

Do

When using the Expand / Collapse Button with the Side Panel, provide a Tooltip to label the icon only Tertiary Button variant. When the Side Panel is expanded, the Tooltip contains the text "Collapse" and when collapsed, the Tooltip reads "Expand."

Examples

Basic Example

SidePanel is composed of three parts:

  • The panel container (with an optional model prop)
  • A heading (SidePanel.Heading) for the panel that is visually hidden when the panel is collapsed
  • A toggle button (SidePanel.ToggleButton) to control the expand / collapse states

Bidirectional support is built into SidePanel. As seen in the example below, CSS Flexbox flips the page layout and the panel’s contents. SidePanel also has logic to flip the position and direction of the ToggleButton as well as the direction of the expand / collapse animation. If you’re using CSS Flexbox for layouts and using the provided components, you shouldn’t have to provide any custom logic or styling for bidirectional support.

Tasks Panel

import {rocketIcon} from '@workday/canvas-expressive-icons-web';
import {ExpressiveIcon} from '@workday/canvas-kit-react/icon';
import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel} from '@workday/canvas-kit-react/side-panel';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const flexHeadingStyles = createStyles({
  alignItems: 'center',
  gap: system.gap.sm,
});

const viewPortStyles = createStyles({
  height: px2rem(320),
});

export default () => {
  return (
    <Flex cs={viewPortStyles}>
      <SidePanel>
        <SidePanel.Heading>
          <Flex cs={flexHeadingStyles}>
            <ExpressiveIcon icon={rocketIcon} size="xs" />
            Tasks Panel
          </Flex>
        </SidePanel.Heading>
        <SidePanel.ToggleButton aria-label="Collapse View" />
      </SidePanel>
    </Flex>
  );
};

Hidden Name

SidePanel’s <section> element container should always have an accessible name to help screen reader users understand the purpose of the panel. For this reason, we recommend using the SidePanel.Heading component and setting the hidden prop to true. This will visually hide the heading while keeping it accessible to screen readers.

Side Panel with a hidden title text.

import * as React from 'react';

import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';

const stylesOverride = {
  viewport: createStyles({
    height: px2rem(320),
  }),
  main: createStyles({
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    flex: 1,
    flexBasis: 'auto',
  }),
};

export default () => {
  const model = useSidePanelModel({
    onStateTransition: state => {
      console.log(`state is: ${state}`);
    },
  });

  return (
    <Flex cs={stylesOverride.viewport}>
      <SidePanel model={model}>
        <SidePanel.ToggleButton aria-label="Collapse View" />
        <SidePanel.Heading hidden size="small">
          Tasks Panel
        </SidePanel.Heading>
      </SidePanel>
      <Flex as="main" cs={stylesOverride.main}>
        <Text as="p" typeLevel="body.large">
          Side Panel with a hidden title text.
        </Text>
      </Flex>
    </Flex>
  );
};

Variants

SidePanel supports three variants, which you supply as a top-level variant prop. They differ only in surface color and depth — the layout, animation, and accessibility behavior is identical.

VariantSurfaceDepthUse for
standardsystem.legacy.color.surface.navigationNoneThe default. Panels that are part of the page layout, such as navigation.
alternativesystem.legacy.color.surface.raisedNonePanels that need to stand out from the page background while staying in-flow.
overlaysystem.legacy.color.surface.default6Panels that need to look lifted off the page. Styling only — see below.

Alternative

The alternative variant uses a raised surface color with no depth. Because it doesn’t cast a shadow, it remains part of the page layout and is a good fit when the panel sits next to content on a tinted or gray background.

Alternative Panel

import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel} from '@workday/canvas-kit-react/side-panel';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';

const viewportStyles = createStyles({
  height: px2rem(320),
});

export default () => {
  return (
    <Flex cs={viewportStyles}>
      <SidePanel variant="alternative">
        <SidePanel.ToggleButton aria-label="Collapse View" />
        <SidePanel.Heading size="small">Alternative Panel</SidePanel.Heading>
      </SidePanel>
    </Flex>
  );
};

Overlay

The overlay variant is a visual treatment only. It gives the panel the default surface color and depth 6 so it reads as floating above the page, which is the same elevation Modal.Card uses. The name describes how the panel looks; it does not turn SidePanel into a dialog. This variant is the alternate variant from @workday/canvas-kit-preview-react, renamed — it has never carried any behavior beyond the surface and shadow.

Reach for it when a panel needs to look lifted off the page — for example an overlay menu or a filter panel you position over content with your own CSS. SidePanel still renders a <section> that participates in the page and the tab order exactly like standard and alternative do.

Setting variant="overlay" does not add any of the following, and SidePanel has no props to opt into them:

  • role="dialog" or aria-modal — the element stays a plain <section> named by its heading
  • A focus trap, initial focus, or return focus
  • A backdrop / overlay element
  • Close on Escape or close on outside click
  • aria-hidden on sibling content, or body scroll locking
  • Positioning — the panel is in normal flow until you position it yourself

Accessibility Note: If you need a panel that genuinely blocks the rest of the page, use Modal instead of assembling that behavior around SidePanel. Modal composes useInitialFocus, useReturnFocus, useFocusTrap, useCloseOnEscape, useCloseOnOverlayClick, useAssistiveHideSiblings, and useDisableBodyScroll and is tested as a unit. Those hooks are built on usePopupModel and cannot be added to useSidePanelModel, so a hand-rolled version on top of SidePanel will not get you the same guarantees.

Keyboard and focus behavior: because the variant adds no dialog semantics, keyboard behavior is identical to the other two variants and no focus management is expected of you:

  • Tab / Shift + Tab move through the panel’s focusable elements in DOM order and then continue into the rest of the page. Focus is not trapped.
  • Focus does not move when the panel expands or collapses. It stays on SidePanel.ToggleButton, which reports the new state through aria-pressed.
  • Escape does nothing. Collapsing happens only through the toggle button or model.events.collapse().
  • Collapsing hides the panel’s content visually but does not remove it from the DOM. Keep the toggle button as the first focusable element in the panel — see Accessibility — so keyboard users reach the control before the content it hides.

If you do position this variant over other content, treat it as a non-modal region: the content behind it stays reachable by keyboard and by screen reader, and users can tab out of the panel while it visually covers the page. That mismatch between what the panel looks like and how it behaves is the reason to prefer Modal for anything the user must dismiss before continuing.

Overlay Panel

Toggle the content direction

import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel} from '@workday/canvas-kit-react/side-panel';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

// local helper hook for setting content direction;
import {useDirection} from './useDirection';

const stylesOverride = {
  viewport: createStyles({
    height: px2rem(320),
    backgroundColor: system.color.bg.alt.default,
  }),
  main: createStyles({
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    flex: 1,
    flexBasis: 'auto',
  }),
};

export default () => {
  const {direction, toggleDirection} = useDirection();

  return (
    <CanvasProvider dir={direction}>
      <Flex cs={stylesOverride.viewport}>
        <SidePanel variant="overlay">
          <SidePanel.ToggleButton aria-label="Collapse View" />
          <SidePanel.Heading size="small">Overlay Panel</SidePanel.Heading>
        </SidePanel>
        <Flex as="main" cs={stylesOverride.main}>
          <Text as="p" typeLevel="body.large">
            Toggle the content direction
          </Text>
          <SecondaryButton onClick={toggleDirection}>
            Set to {direction === 'ltr' ? 'Right-to-Left' : 'Left-to-Right'}
          </SecondaryButton>
        </Flex>
      </Flex>
    </CanvasProvider>
  );
};

External Control

Sometimes you’ll want to control SidePanel’s expand / collapse behavior from outside the component. You can use the model’s events (model.events.expand() and model.events.collapse()) to programmatically control the panel.

Notes about accessibility

When using external controls, be mindful of accessibility:

  • Use aria-pressed on toggle buttons to indicate the current state
  • The SidePanel.ToggleButton inside the panel automatically receives the correct ARIA attributes
  • External buttons should have their own accessible labels (don’t rely on aria-labelledby pointing to the panel’s label)

In the following example, we use the model’s transitionState to determine the button’s pressed state and call model.events.expand() or model.events.collapse() on click.

Control the panel externally

import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const stylesOverride = {
  viewport: createStyles({
    height: px2rem(320),
  }),
  panel: createStyles({
    alignItems: 'center',
    padding: system.padding.md,
  }),
  panelHeading: createStyles({
    color: system.color.fg.muted.strong,
  }),
  main: createStyles({
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    flex: 1,
    flexBasis: 'auto',
  }),
};

export default () => {
  const model = useSidePanelModel({
    initialTransitionState: 'collapsed',
    labelId: 'tasks-panel-label',
  });

  return (
    <Flex cs={stylesOverride.viewport}>
      <SidePanel model={model}>
        <SidePanel.ToggleButton aria-label="Collapse View" />
        <SidePanel.Heading size="small" cs={stylesOverride.panelHeading}>
          Task Panel
        </SidePanel.Heading>
        {model.state.transitionState === 'expanded' && (
          <Flex cs={stylesOverride.panel}>Contents</Flex>
        )}
      </SidePanel>
      <Flex as="main" cs={stylesOverride.main}>
        <Text as="p" typeLevel="body.large">
          Control the panel externally
        </Text>
        <SecondaryButton
          onClick={
            model.state.transitionState === 'expanded' ? model.events.collapse : model.events.expand
          }
          aria-pressed={model.state.transitionState === 'expanded'}
        >
          {model.state.transitionState === 'expanded' ? 'Hide Side Panel' : 'Show Side Panel'}
        </SecondaryButton>
      </Flex>
    </Flex>
  );
};

Right Origin

By default, SidePanel uses a start origin (left in LTR, right in RTL). This sets the ToggleButton’s position and direction as well as the direction of the animation. You can set the origin to "end" to flip these. The origin uses logical properties (start/end) for proper bidirectional support.

Toggle the content direction

Tasks Panel

import {SecondaryButton} from '@workday/canvas-kit-react/button';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel, useSidePanelModel} from '@workday/canvas-kit-react/side-panel';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';

// local helper hook for setting content direction;
import {useDirection} from './useDirection';

const stylesOverride = {
  viewport: createStyles({
    height: px2rem(320),
  }),
  panelContainer: createStyles({
    marginInlineStart: 'auto',
  }),
  panel: createStyles({
    alignItems: 'center',
    justifyContent: 'flex-end',
  }),
  main: createStyles({
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    flex: 1,
    flexBasis: 'auto',
  }),
};

const RightPanel = () => {
  const model = useSidePanelModel({
    origin: 'end',
  });

  return (
    <SidePanel model={model} className={stylesOverride.panelContainer}>
      <SidePanel.ToggleButton aria-label="Collapse View" />
      <Flex cs={stylesOverride.panel}>
        <SidePanel.Heading size="small">Tasks Panel</SidePanel.Heading>
      </Flex>
    </SidePanel>
  );
};

export default () => {
  const {direction, toggleDirection} = useDirection();

  return (
    <CanvasProvider dir={direction}>
      <Flex cs={stylesOverride.viewport}>
        <Flex as="main" cs={stylesOverride.main}>
          <Text as="p" typeLevel="body.large">
            Toggle the content direction
          </Text>
          <SecondaryButton onClick={toggleDirection}>
            Set to {direction === 'ltr' ? 'Right-to-Left' : 'Left-to-Right'}
          </SecondaryButton>
        </Flex>

        <RightPanel />
      </Flex>
    </CanvasProvider>
  );
};

Always Open

If you do not need SidePanel’s expand / collapse behavior, you can simply omit the ToggleButton.

Tasks Panel

This is the main content section.

import {rocketIcon} from '@workday/canvas-expressive-icons-web';
import {ExpressiveIcon} from '@workday/canvas-kit-react/icon';
import {Flex} from '@workday/canvas-kit-react/layout';
import {SidePanel} from '@workday/canvas-kit-react/side-panel';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
import {system} from '@workday/canvas-tokens-web';

const stylesOverride = {
  accentIcon: createStyles({
    marginInlineEnd: system.gap.md,
  }),
  pageContainer: createStyles({
    gap: system.gap.md,
    height: px2rem(320),
  }),
  panelContainer: createStyles({
    alignItems: 'center',
    padding: system.padding.md,
  }),
  panelHeading: createStyles({
    color: system.color.fg.default,
  }),
  mainContent: createStyles({
    alignItems: 'center',
    justifyContent: 'center',
    flexBasis: 'auto',
    flex: 1,
  }),
};

export default () => {
  return (
    <Flex cs={stylesOverride.pageContainer}>
      <SidePanel initialTransitionState="expanded">
        <Flex cs={stylesOverride.panelContainer}>
          <ExpressiveIcon icon={rocketIcon} cs={stylesOverride.accentIcon} />
          <SidePanel.Heading size="small" cs={stylesOverride.panelHeading}>
            Tasks Panel
          </SidePanel.Heading>
        </Flex>
      </SidePanel>
      <Flex as="main" cs={stylesOverride.mainContent}>
        <Text as="p" typeLevel="body.large">
          This is the main content section.
        </Text>
      </Flex>
    </Flex>
  );
};

Deriving Expanded State

If you need a simple boolean expanded state (similar to the preview-react onExpandedChange callback), you can derive it from the transitionState using the onStateTransition callback on the model.

onStateTransition

The onStateTransition callback is called whenever the panel’s transition state changes. This includes all four states: expanding, expanded, collapsing, and collapsed. You can pass this callback directly to the SidePanel component or to the useSidePanelModel hook.

The transition flow is:

  1. Collapsing: expanded → collapsing → collapsed
  2. Expanding: collapsed → expanding → expanded

This is useful for:

  • Triggering side effects when the panel state changes
  • Syncing the panel state with external state management
  • Animating child components based on the transition state

Side panel is expanded.

import * as React from 'react';

import {AccessibleHide} from '@workday/canvas-kit-react/common';
import {Flex} from '@workday/canvas-kit-react/layout';
import {
  SidePanel,
  SidePanelTransitionStates,
  useSidePanelModel,
} from '@workday/canvas-kit-react/side-panel';
import {Text} from '@workday/canvas-kit-react/text';
import {createStyles, px2rem} from '@workday/canvas-kit-styling';

const stylesOverride = {
  viewport: createStyles({
    height: px2rem(320),
  }),
  main: createStyles({
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'column',
    flex: 1,
    flexBasis: 'auto',
  }),
};

export default () => {
  const [transitionState, setTransitionState] =
    React.useState<SidePanelTransitionStates>('expanded');

  const model = useSidePanelModel({
    onStateTransition: state => {
      setTransitionState(state);
      console.log('Expanded changed to:', state);
    },
  });

  return (
    <Flex cs={stylesOverride.viewport}>
      <SidePanel model={model}>
        <SidePanel.ToggleButton aria-label="Collapse View" />
        <SidePanel.Heading hidden size="small">
          Hidden Title
        </SidePanel.Heading>
      </SidePanel>
      <Flex as="main" cs={stylesOverride.main}>
        <Text as="p" typeLevel="body.large">
          Side panel is {transitionState}.
        </Text>
      </Flex>
    </Flex>
  );
};

Accessibility

SidePanel renders a <section> element with an accessible name provided by aria-labelledby, which references the SidePanel.Heading component. This ensures screen reader users understand the purpose of the panel.

Panel and Heading

  • The SidePanel.Heading provides the accessible name for the panel via aria-labelledby
  • When the panel is collapsed, the heading is automatically hidden visually but remains accessible to screen readers
  • Use the hidden prop on SidePanel.Heading if you want the heading always visually hidden

Toggle Button

  • SidePanel.ToggleButton automatically includes aria-controls (references the panel’s id), aria-pressed (indicates current state), and aria-describedby (references the panel’s heading)
  • Developers must provide a static aria-label string on SidePanel.ToggleButton to describe the button’s purpose (e.g., “Collapse View”). Avoid using ambiguous terms like “Toggle” in the label. Since aria-pressed communicates the state, avoid dynamically updating aria-label
  • The button includes a Tooltip with customizable text via tooltipTextExpand and tooltipTextCollapse props (defaults: “Expand View” and “Collapse View”)
  • For optimal keyboard navigation, place SidePanel.ToggleButton as the first focusable element in the panel

Component API

SidePanel

Props

Props extend from section. Changing the as prop will change the element interface.

Props extend from . If a model is passed, props from SidePanelModelConfig are ignored.

NameTypeDescriptionDefault
collapsedWidth number | string

The width of the component (in px if it's a number) when it is collapsed.

64
expandedWidth number | string

The width of the component (in px if it's a number) when it is expanded.

320
variant

The style variant of the side panel.

  • 'standard': navigation surface background (system.legacy.color.surface.navigation), no depth.
  • 'alternative': raised surface background (system.legacy.color.surface.raised), no depth.
  • 'overlay': default surface background (system.legacy.color.surface.default) with level 6 depth, for panels that need to look lifted off the page. This is a visual treatment only — it adds no dialog semantics, focus trapping, or backdrop.
'standard'
childrenReactNode
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

import {handleCsProp} from '@workday/canvas-kit-styling'; import {mergeStyles} from '@workday/canvas-kit-react/layout'; // ... // `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same // function signature, but adds support for style props. return ( <Element {...handleCsProp(elemProps, [ myStyles, myModifiers({ size: 'medium' }), myVars({ backgroundColor: 'red' }) ])} > {children} </Element> )
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

section
refReact.Ref<R = section>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useSidePanelContainer

Adds the necessary props to the SidePanel container element. This includes the id and aria-labelledby attributes for accessibility.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  id: string;
  aria-labelledby: string;
  onTransitionEnd: (event: <>) => <>;
}

SidePanel.ToggleButton

SidePanel.ToggleButton is a control that toggles between expanded and collapsed states. It must be used within the SidePanel component as a child. For accessibility purposes, it should be the first focusable element in the panel.

The button automatically receives aria-controls (the panel's id), aria-pressed (true when the panel is collapsed), and aria-describedby (the heading's id) from the model. Provide a static aria-label for the button's accessible name — aria-pressed already conveys the state, so the label should not change between states.

Props

Props extend from button. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useSidePanelToggleButton

Adds the necessary ARIA attributes to the SidePanel's toggle button. This includes aria-controls pointing at the panel, aria-pressed to convey the collapsed state, and aria-describedby pointing at the panel's heading.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  aria-controls: string;
  aria-pressed: boolean;
  aria-describedby: string;
}

SidePanel.Heading

SidePanel.Heading is a styled heading that provides the accessible name for the SidePanel. The heading's id is automatically linked to the panel's aria-labelledby attribute. By default, the heading is hidden when the panel is collapsed.

Layout Component

SidePanel.Heading supports all props from thelayout component.

Props

Props extend from . Changing the as prop will change the element interface.

NameTypeDescriptionDefault
size 'large' | 'medium' | 'small'

The size of the heading.

'small'
childrenReactNode
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

import {handleCsProp} from '@workday/canvas-kit-styling'; import {mergeStyles} from '@workday/canvas-kit-react/layout'; // ... // `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same // function signature, but adds support for style props. return ( <Element {...handleCsProp(elemProps, [ myStyles, myModifiers({ size: 'medium' }), myVars({ backgroundColor: 'red' }) ])} > {children} </Element> )
variant 'error' | 'hint' | 'inverse'

Type variant token names: error, hint or inverse.

<Text variant="error" typeLevel="subtext.large">Error text</Text>
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

refReact.Ref<R = >

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useSidePanelHeading

Adds the necessary props to the SidePanelHeading subcomponent. This sets the id to the labelId from the model for accessibility purposes, and hides the heading when the panel is not expanded.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  id: string;
  hidden: boolean;
}

Model

useSidePanelModel

useSidePanelModel (config: ):

Hooks

useSidePanelModel

The useSidePanelModel hook creates a model for managing the SidePanel’s state and events. You can pass this model to the SidePanel component, or let the component create one internally.

import {useSidePanelModel} from '@workday/canvas-kit-react/side-panel'; // Create a model with custom configuration const model = useSidePanelModel({ initialTransitionState: 'collapsed', origin: 'end', onStateTransition: state => console.log('State:', state), }); // Access state model.state.transitionState; // 'expanded' | 'expanding' | 'collapsed' | 'collapsing' model.state.panelId; // unique ID for the panel model.state.labelId; // unique ID for the label // Trigger events model.events.expand(); // Set to expanded (no animation) model.events.collapse(); // Set to collapsed (no animation) model.events.handleAnimationStart(); // Start expand/collapse animation

useSidePanelModel

useSidePanelModel (config: ):

useSidePanelContainer

The useSidePanelContainer elemProps hook provides the necessary props for the SidePanel container element, including id, aria-labelledby, and onTransitionEnd.

useSidePanelContainer

Adds the necessary props to the SidePanel container element. This includes the id and aria-labelledby attributes for accessibility.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  id: string;
  aria-labelledby: string;
  onTransitionEnd: (event: <>) => <>;
}

useSidePanelToggleButton

The useSidePanelToggleButton elemProps hook provides ARIA attributes for the toggle button, including aria-controls, aria-pressed, and aria-describedby.

useSidePanelToggleButton

Adds the necessary ARIA attributes to the SidePanel's toggle button. This includes aria-controls pointing at the panel, aria-pressed to convey the collapsed state, and aria-describedby pointing at the panel's heading.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  aria-controls: string;
  aria-pressed: boolean;
  aria-describedby: string;
}

Accessibility Guidelines

How Side Panels Impact the Accessible Experience

One of the most important aspects of Side Panel is understanding when the Side Panel’s content begins and ends in the context of the holistic design. Side Panel uses a “landmark region” to help establish such boundaries of the Side Panel’s content for non-visual screen reader users. Including semantic heading text at the top of the Side Panel is recommended to help reinforce the beginning of Side Panel content, and convey the intended purpose of the section. Finally, users must be able to understand whether the Side Panel content is expanded or collapsed on the screen.

Keyboard Interaction

Each interactive component inside Side Panel must have a focus indicator that is highly visible against the background and against the non-focused state. Refer to Accessible Colors for more information.

Side Panel must support the following keyboard interactions:

  • Tab: focus the Side Panel toggle button, and any other interactive components inside Side Panel
  • Enter or Space: activates Side Panel toggle button

Screen Reader Interaction

Side Panel must communicate the following to users:

  • The Side Panel is a landmark region, named by the Side Panel’s heading text
  • The “expanded” or “collapsed” state of the Side Panel

Design Annotations Needed

  • Specify when the Side Panel is used for navigation
  • Specify heading level at the top of SIde Panel

Implementation Markup Needed

  • Use semantic heading text at the top of Side Panel to describe the purpose of the content included inside of Side Panel.
  • [Included in component] Use a semantic <section> element and an aria-labelledby reference to create a landmark region for screen readers.
  • When Side Panel is used for navigation purposes, use the as prop to change the rendered element from the default <section> to a <nav> element.
  • [Included in component] An accessible Tooltip component is included on the Side Panel toggle button describing what the icon button will do when activated.
  • [Included in component] The toggle button must have an accessible name using either an aria-labelledby reference or an aria-label string.
  • [Included in component] The toggle button must convey the Side Panel state using the aria-expanded property.