Skip to main content

Sidebar

Overview​

The Sidebar component provides a slide-out navigation panel that appears from the left or right side of the screen.

It's ideal for mobile navigation, settings panels, or any content that should overlay the main interface.


Import​

import { Sidebar } from '@allxsmith/bestax-bulma';

Usage​

info

The examples below use the inline prop so the sidebar renders inside the live preview. In real apps, omit inline so the sidebar uses a portal for proper stacking above all page content.

Basic Sidebar​

A simple sidebar with navigation menu. Compose the sidebar with Sidebar.Header, Sidebar.Title, Sidebar.Close, and Sidebar.Body subcomponents.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="primary" onClick={() => setIsOpen(true)}>
        Open Sidebar
      </Button>
      <Sidebar inline isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <Sidebar.Header>
          <Sidebar.Title>Navigation</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Menu>
            <Menu.Label>General</Menu.Label>
            <Menu.List>
              <Menu.Item href="#">Dashboard</Menu.Item>
              <Menu.Item active href="#">
                Settings
              </Menu.Item>
              <Menu.Item href="#">Profile</Menu.Item>
            </Menu.List>
            <Menu.Label>Administration</Menu.Label>
            <Menu.List>
              <Menu.Item href="#">Users</Menu.Item>
              <Menu.Item href="#">Permissions</Menu.Item>
            </Menu.List>
          </Menu>
        </Sidebar.Body>
      </Sidebar>
    </>
  );
}


Right-side Sidebar​

Sidebar that slides in from the right.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="info" onClick={() => setIsOpen(true)}>
        Open Right Sidebar
      </Button>
      <Sidebar
        inline
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        position="right"
      >
        <Sidebar.Header>
          <Sidebar.Title>Settings</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Paragraph>Configure your preferences here.</Paragraph>
        </Sidebar.Body>
      </Sidebar>
    </>
  );
}


Custom Width​

Sidebar with custom width.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="success" onClick={() => setIsOpen(true)}>
        Open Wide Sidebar
      </Button>
      <Sidebar
        inline
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        width="400px"
      >
        <Sidebar.Header>
          <Sidebar.Title>Wide Panel</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Paragraph>This sidebar has a custom width of 400px.</Paragraph>
        </Sidebar.Body>
      </Sidebar>
    </>
  );
}


Full Width (Mobile Style)​

Full-width sidebar for mobile navigation.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="warning" onClick={() => setIsOpen(true)}>
        Open Full Width
      </Button>
      <Sidebar
        inline
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        isFullwidth
      >
        <Sidebar.Header>
          <Sidebar.Title>Full Width Panel</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Menu>
            <Menu.List>
              <Menu.Item href="#">Home</Menu.Item>
              <Menu.Item href="#">Products</Menu.Item>
              <Menu.Item href="#">About</Menu.Item>
              <Menu.Item href="#">Contact</Menu.Item>
            </Menu.List>
          </Menu>
        </Sidebar.Body>
      </Sidebar>
    </>
  );
}


Without Overlay​

Sidebar without the background overlay.

caution

When overlay={false}, there's no backdrop to click for dismissal. Always provide an explicit Close action inside the sidebar (or rely on the Escape key) so users can close it.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="link" onClick={() => setIsOpen(true)}>
        Open Without Overlay
      </Button>
      <Paragraph mt="4">
        You can still interact with the page content when the sidebar is open.
      </Paragraph>
      <Sidebar
        inline
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        overlay={false}
      >
        <Sidebar.Header>
          <Sidebar.Title>No Overlay</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Menu>
            <Menu.List>
              <Menu.Item href="#">Home</Menu.Item>
              <Menu.Item href="#">About</Menu.Item>
              <Menu.Item href="#">Contact</Menu.Item>
            </Menu.List>
          </Menu>
          <Button color="primary" mt="4" onClick={() => setIsOpen(false)}>
            Close Sidebar
          </Button>
        </Sidebar.Body>
      </Sidebar>
    </>
  );
}


Non-cancelable Sidebar​

Sidebar that cannot be dismissed by overlay click or escape key.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="danger" onClick={() => setIsOpen(true)}>
        Open Important Panel
      </Button>
      <Sidebar
        inline
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        overlayClose={false}
        escapeClose={false}
        canCancel={false}
      >
        <Sidebar.Header>
          <Sidebar.Title>Important Action</Sidebar.Title>
        </Sidebar.Header>
        <Sidebar.Body>
          <Paragraph>You must complete this action before closing.</Paragraph>
          <Button color="primary" onClick={() => setIsOpen(false)} mt="4">
            Complete & Close
          </Button>
        </Sidebar.Body>
      </Sidebar>
    </>
  );
}


Use Sidebar.Footer for actions or metadata pinned to the bottom of the sidebar.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="primary" onClick={() => setIsOpen(true)}>
        Open With Footer
      </Button>
      <Sidebar inline isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <Sidebar.Header>
          <Sidebar.Title>Navigation</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Menu>
            <Menu.Label>Pages</Menu.Label>
            <Menu.List>
              <Menu.Item active href="#">
                Home
              </Menu.Item>
              <Menu.Item href="#">Products</Menu.Item>
              <Menu.Item href="#">Services</Menu.Item>
              <Menu.Item href="#">Blog</Menu.Item>
              <Menu.Item href="#">Contact</Menu.Item>
            </Menu.List>
          </Menu>
        </Sidebar.Body>
        <Sidebar.Footer>
          <Paragraph textSize="7" textColor="grey">
            © 2026 Company Name
          </Paragraph>
        </Sidebar.Footer>
      </Sidebar>
    </>
  );
}


Compound (dot-notation) usage​

Sidebar.Header, Sidebar.Title, Sidebar.Close, Sidebar.Body, and Sidebar.Footer are all statics on Sidebar, so the whole panel can be composed from the single Sidebar import.

function example() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button color="primary" onClick={() => setIsOpen(true)}>
        Open Sidebar
      </Button>
      <Sidebar inline isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <Sidebar.Header>
          <Sidebar.Title>Dot Notation</Sidebar.Title>
          <Sidebar.Close onClick={() => setIsOpen(false)} />
        </Sidebar.Header>
        <Sidebar.Body>
          <Paragraph>
            Header, Title, Close, Body, and Footer are all available from the
            single Sidebar import.
          </Paragraph>
        </Sidebar.Body>
        <Sidebar.Footer>
          <Button color="primary" onClick={() => setIsOpen(false)}>
            Done
          </Button>
        </Sidebar.Footer>
      </Sidebar>
    </>
  );
}


Close Methods​

When canCancel is true, the sidebar can be closed by:

  1. Clicking the overlay - When overlayClose is true
  2. Pressing Escape key - When escapeClose is true
  3. Calling onClose - From a button or other action

Accessibility​

  • Uses role="dialog" for proper screen reader announcement
  • Has aria-modal="true" when overlay is shown
  • Focus moves into the sidebar when it opens, to its first tab stop or the sidebar itself, unless it has no overlay and a modal overlay is open under it (see below). That's decided as it opens, so changing overlay while it's open leaves focus where it is. When it closes, focus goes back to whatever had it when the sidebar opened, unless focus has already left the sidebar, say for an overlay that's still open on top
  • With an overlay, Tab and Shift+Tab stay inside the sidebar while it's the topmost modal overlay, so the keyboard order agrees with what aria-modal="true" tells assistive technology. They wrap at the same tab stops useFocusTrap does, so hidden, disabled and tabIndex={-1} elements are skipped and a radio group counts once. A Tab from outside the sidebar brings focus back in, and so does a Tab the browser takes out anyway, as Safari does past links when its settings say to
  • A Modal or Dialog opened over the sidebar, including one rendered inside it, holds Tab until it closes, and then the sidebar holds it again. A sidebar with an overlay opened over a Modal holds Tab until it closes, and then the Modal does
  • A focus trap of your own inside the sidebar, such as a portaled panel's, keeps a Tab it calls preventDefault() on, and the sidebar leaves focus where your trap put it
  • Without an overlay the sidebar isn't modal and holds no Tab. On the page, Tab moves in and out of it freely. Opened over a Modal, or over a sidebar with an overlay, it takes Escape first, but the one under it keeps holding Tab and keeps focus: the sidebar doesn't take focus when it opens, and a Tab from its controls, once a click puts focus there, goes back into that one
  • A closed sidebar stays in the page so it can slide out, and it's inert and aria-hidden while closed: Tab passes over its content and assistive technology doesn't read it
  • Escape key closes the sidebar by default. One Escape closes the topmost overlay only: a Modal or Dialog opened over the sidebar closes first, and the sidebar on the next Escape. A sidebar opened over a Modal closes before it
  • A sidebar on top that can't close on Escape, with escapeClose or canCancel off, still keeps the key from the overlays under it. A Toast waits until no overlay is open
  • A popup open inside it, such as a Dropdown menu or a date picker's calendar, takes the first Escape, and the next one closes the sidebar. A popup of your own does the same by calling preventDefault() on the Escape it closes on, as the Modal page explains.
  • Body scroll is prevented when the sidebar is open with an overlay, through the ref-counted lock shared with Modal, Dialog and Loading — so closing one overlay never unlocks the page underneath another that is still open

  • Modal - For centered modal dialogs
  • Menu - For navigation menus inside sidebar

Additional Resources​

Pro Tip

Use the Sidebar with a Menu component for building navigation drawers in mobile-responsive layouts.


Props​

PropTypeDefaultDescription
isOpenboolean—Whether the sidebar is open (required). Opening moves focus into the sidebar, to its first tab stop or the sidebar itself, except for a sidebar without an overlay opened while a modal overlay, such as a Modal, is open: that one leaves focus where it is. That's decided as it opens, and changing overlay while it's open doesn't move focus. Closing hands focus back to whatever had it when the sidebar opened, unless focus has already left the sidebar, as it does for an overlay opened over it. A closed sidebar stays in the page, so it can slide out, and is inert and aria-hidden there: Tab passes over its content and assistive technology doesn't read it.
onClose() => void—Callback when sidebar should close.
position'left' | 'right''left'Which side the sidebar appears from. Default: 'left'.
widthstring'260px'Custom width of the sidebar.
isFullwidthbooleanfalseSidebar takes full width (mobile-style).
fullWidthbooleanfalseDeprecated. Use isFullwidth instead — isFullwidth wins if both are set. Sidebar takes full width (mobile-style).
overlaybooleantrueShow an overlay behind the sidebar, which makes it modal: it gets aria-modal="true", locks body scroll, and keeps Tab and Shift+Tab inside it while it is the topmost modal overlay. A Modal or Dialog opened over it, even one rendered inside it, holds Tab until it closes. Without an overlay the sidebar isn't modal and holds no Tab: on the page, Tab moves in and out of it freely, and opened over a Modal, it leaves the Modal holding Tab, and focus, while still taking Escape first. Whether the sidebar takes focus is decided as it opens: changing this while it's open moves the Tab trap, not focus. Default: true.
overlayClosebooleantrueClose sidebar when overlay is clicked. Default: true.
escapeClosebooleantrueClose sidebar on Escape key. Only the topmost open overlay responds, whichever Modal, Dialog, Sidebar or full-page Loading opened last, so Escape closes one layer at a time. The sidebar marks the Escape it closes on as used with preventDefault(), and a Toast waits until no overlay is open. Turned off, a sidebar on top still keeps Escape from the layers under it. A popup inside it, such as a Dropdown's menu or a date picker's calendar, takes its own Escape first, and so does content that calls preventDefault() on it. Default: true.
canCancelbooleantrueAllow closing the sidebar. Default: true.
childrenReact.ReactNode—Content to display in the sidebar.
inlinebooleanfalseRenders inline instead of using a portal.
classNamestring—Additional CSS classes.
refReact.Ref<HTMLElement>—Ref forwarded to the sidebar element.
...All standard HTML attributes and Bulma helper props—See Helper Props

Subcomponents:

  • Sidebar.Header: Container for the sidebar header.
  • Sidebar.Title: Title text inside the header.
  • Sidebar.Close: Close button for the sidebar.
  • Sidebar.Body: Main content area of the sidebar.
  • Sidebar.Footer: Footer area of the sidebar.

Sidebar.Header​

PropTypeDefaultDescription
childrenReact.ReactNode—Header content.
classNamestring—Additional CSS classes.
...All standard <div> attributes—

Sidebar.Title​

PropTypeDefaultDescription
childrenReact.ReactNode—Title content.
classNamestring—Additional CSS classes.
...All standard <p> attributes—

Sidebar.Close​

PropTypeDefaultDescription
classNamestring—Additional CSS classes.
childrenReact.ReactNode—Content rendered inside the component.
type'button' | 'submit' | 'reset''button'Button type. Defaults to 'button', so a close button inside a form does not submit it. Pass 'submit' or 'reset' and yours is used; any other value, or a spread carrying type: undefined, renders 'button'.
aria-labelstring'Close'Accessible name. Pass your own to replace it. An empty one, or a spread carrying 'aria-label': undefined, keeps the default rather than leaving the button unnamed.
...All standard <button> attributes—

Sidebar.Body​

PropTypeDefaultDescription
childrenReact.ReactNode—Body content.
classNamestring—Additional CSS classes.
...All standard <div> attributes—

Sidebar.Footer​

PropTypeDefaultDescription
childrenReact.ReactNode—Footer content.
classNamestring—Additional CSS classes.
...All standard <div> attributes—

CSS & Sass Variables​

Sidebar registers these variables on its own .sidebar element. Override them there (or via className) — a value set on an ancestor is only inherited, and loses to the component-level declaration. See Theme.

CSS VariableSass VariableDefault
--bulma-sidebar-width$sidebar-width260px
--bulma-sidebar-background$sidebar-backgroundvar(--bulma-scheme-main)
--bulma-sidebar-shadow$sidebar-shadow0 2px 8px hsla(0, 0%, 0%, 0.15)
--bulma-sidebar-transition-duration$sidebar-transition-duration0.3s
--bulma-sidebar-content-padding$sidebar-content-padding1rem
--bulma-sidebar-scrollbar-width$sidebar-scrollbar-width6px
--bulma-sidebar-scrollbar-color$sidebar-scrollbar-colorvar(--bulma-grey-light)
--bulma-sidebar-scrollbar-color-hover$sidebar-scrollbar-color-hovervar(--bulma-grey)
--bulma-sidebar-header-padding$sidebar-header-padding0.75rem 1rem
--bulma-sidebar-header-border-color$sidebar-header-border-colorvar(--bulma-border)
--bulma-sidebar-header-margin-bottom$sidebar-header-margin-bottom1rem
--bulma-sidebar-title-size$sidebar-title-size1.25rem
--bulma-sidebar-title-weight$sidebar-title-weightvar(--bulma-weight-semibold)
--bulma-sidebar-title-color$sidebar-title-colorvar(--bulma-text-strong)
--bulma-sidebar-close-size$sidebar-close-size1.75rem
--bulma-sidebar-close-color$sidebar-close-colorvar(--bulma-text)
--bulma-sidebar-close-line-width$sidebar-close-line-width2px
--bulma-sidebar-close-hover-background$sidebar-close-hover-backgroundvar(--bulma-scheme-main-ter)
--bulma-sidebar-close-radius$sidebar-close-radiusvar(--bulma-radius)
--bulma-sidebar-footer-padding$sidebar-footer-padding0.75rem 1rem
--bulma-sidebar-footer-border-color$sidebar-footer-border-colorvar(--bulma-border)
--bulma-sidebar-footer-margin-top$sidebar-footer-margin-top1rem
--bulma-sidebar-menu-item-padding$sidebar-menu-item-padding0.5rem 0.75rem
--bulma-sidebar-menu-item-radius$sidebar-menu-item-radiusvar(--bulma-radius)
--bulma-sidebar-menu-item-hover-background$sidebar-menu-item-hover-backgroundvar(--bulma-scheme-main-bis)
--bulma-sidebar-menu-item-active-background$sidebar-menu-item-active-backgroundvar(--bulma-link)
--bulma-sidebar-menu-item-active-color$sidebar-menu-item-active-colorvar(--bulma-link-invert)
--bulma-sidebar-static-border-color$sidebar-static-border-colorvar(--bulma-border)
--bulma-sidebar-overlay-background ‡$sidebar-overlay-backgroundhsla(0, 0%, 4%, 0.86)

‡ declared on a constituent element: values set via className, the style prop, or an ancestor are only inherited and lose — target the declaring element in your CSS.