Components
Usage
SideSheet shows secondary content anchored to the left or right edge of the screen. variant="standard" renders persistent layout chrome next to page content; variant="modal" renders a dismissible overlay above it.
Standard
The standard variant is always part of the layout. It defaults to open and has no dismissal affordance beyond its own close button, since it does not block the rest of the page.
Page content
The standard side sheet is persistent layout chrome that spans the full height of the page next to the content it supports, the same way this documentation site's own navigation sidebar does.
Details
Side content
import { SideSheet } from '@udixio/ui-react';
export default function SideSheetStandardReact() {
return (
<div className="flex h-[36rem] w-full overflow-hidden rounded-xl border border-outline">
<div className="flex-1 overflow-y-auto p-4">
<p className="text-title-medium">Page content</p>
<p className="text-body-medium text-on-surface-variant">
The standard side sheet is persistent layout chrome that spans the
full height of the page next to the content it supports, the same
way this documentation site's own navigation sidebar does.
</p>
</div>
<SideSheet title="Details" position="right">
<p className="p-4 text-body-medium text-on-surface-variant">
Side content
</p>
</SideSheet>
</div>
);
}Page content
The standard side sheet is persistent layout chrome that spans the full height of the page next to the content it supports, the same way this documentation site's own navigation sidebar does.
Details
Side content
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { SideSheet } from '@udixio/ui-angular';
@Component({
selector: 'docs-side-sheet-standard-angular',
standalone: true,
imports: [SideSheet],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div
class="flex h-[36rem] w-full overflow-hidden rounded-xl border border-outline"
>
<div class="flex-1 overflow-y-auto p-4">
<p class="text-title-medium">Page content</p>
<p class="text-body-medium text-on-surface-variant">
The standard side sheet is persistent layout chrome that spans the
full height of the page next to the content it supports, the same
way this documentation site's own navigation sidebar does.
</p>
</div>
<lib-side-sheet title="Details" position="right">
<p class="p-4 text-body-medium text-on-surface-variant">
Side content
</p>
</lib-side-sheet>
</div>
`,
})
export class SideSheetStandardAngular {}Modal
The modal variant renders a backdrop above the page and behaves like a dialog: it traps focus, closes on Escape or a backdrop click, and restores focus to the element that opened it.
Side sheet: closed
import { useState } from 'react';
import { Button, SideSheet } from '@udixio/ui-react';
export default function SideSheetModalReact() {
const [open, setOpen] = useState(false);
const [container, setContainer] = useState<HTMLDivElement | null>(null);
return (
<div
ref={setContainer}
className="relative min-h-80 w-full overflow-hidden rounded-xl [contain:layout]"
>
<div className="grid h-full content-between gap-8 p-4">
<div
className="rounded-xl border border-outline p-4"
role="status"
aria-live="polite"
>
<p className="text-title-medium">
Side sheet: {open ? 'open' : 'closed'}
</p>
</div>
<div className="flex justify-end">
<Button label="Open details" onClick={() => setOpen(true)} />
</div>
</div>
<SideSheet
variant="modal"
title="Details"
open={open}
onOpenChange={setOpen}
container={container}
>
<p className="p-4 text-body-medium text-on-surface-variant">
Modal content. Press Escape, click the backdrop, or use the close
button to dismiss it.
</p>
</SideSheet>
</div>
);
}Side sheet: closed
import {
ChangeDetectionStrategy,
Component,
ElementRef,
computed,
viewChild,
} from '@angular/core';
import { Button, SideSheet } from '@udixio/ui-angular';
@Component({
selector: 'docs-side-sheet-modal-angular',
standalone: true,
imports: [Button, SideSheet],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div
#container
class="relative min-h-80 w-full overflow-hidden rounded-xl [contain:layout]"
>
<div class="grid h-full content-between gap-8 p-4">
<div
class="rounded-xl border border-outline p-4"
role="status"
aria-live="polite"
>
<p class="text-title-medium">
Side sheet: {{ open ? 'open' : 'closed' }}
</p>
</div>
<div class="flex justify-end">
<lib-button label="Open details" (click)="open = true" />
</div>
</div>
<lib-side-sheet
variant="modal"
title="Details"
[open]="open"
(openChange)="open = $event"
[container]="containerEl()"
>
<p class="p-4 text-body-medium text-on-surface-variant">
Modal content. Press Escape, click the backdrop, or use the close
button to dismiss it.
</p>
</lib-side-sheet>
</div>
`,
})
export class SideSheetModalAngular {
protected open = false;
private readonly containerRef =
viewChild<ElementRef<HTMLElement>>('container');
protected readonly containerEl = computed(
() => this.containerRef()?.nativeElement,
);
}Controlled state
Use open with onOpenChange (React) or openChange (Angular) for controlled state; defaultOpen initializes uncontrolled state and defaults to true. title labels the header and, through aria-labelledby, the modal dialog itself. divider shows the trailing divider for the standard variant (defaulting to true) and is ignored for the modal variant, which never renders one. container redirects the modal’s portal target away from document.body — useful for confining a demo to a bounded box.
Motion
The panel and, for the modal variant, the backdrop are always present; opening and closing animates the panel’s width and the backdrop’s opacity rather than mounting and unmounting them. The choreography is implemented once with Motion JavaScript in @udixio/core/dom, so React and Angular share the same timing, cleanup, and reduced-motion behavior — no motion/react is used, and Angular is not a static, unanimated adapter. Pass transition to override the default duration; reduced-motion preference applies the end state immediately.
Accessibility
The modal variant renders role="dialog" and aria-modal. Opening it makes every other document.body (or container) child inert — removing it from the tab order and accessibility tree — locks body scroll, and moves focus into the panel. Escape and the backdrop close it and restore focus to the previously focused element. Whichever variant, the panel is inert and aria-hidden while closed. The standard variant never renders a dialog role, focus trap, or Escape handling.
Both adapters share this behavior, plus the same focus/backdrop/scroll-lock implementation, through the framework-independent @udixio/core/dom controller.
Migrating to 3.0
| Before | 3.0 |
|---|---|
extended / defaultExtended (missing) | open / defaultOpen |
onExtendedChange | onExtendedChange → onOpenChange (React), openChange (Angular) |