SupportRail

An edge tab that grows a support panel from its measured box - use it over Sheet when the tab must stay visible.

SupportRail playground

The tab is the only hit target until the panel opens. Escape and the close button shut it.

Install SupportRail

1. New project? One command installs the package and wires everything - see Installation:

bash
pnpm dlx zyncat-ui init

2. Import the component - it loads its own stylesheet:

tsx
import { SupportRail } from '@zyncat/ui/support-rail';

Usage

tsx
import { SupportRail } from '@zyncat/ui/support-rail';
 
<SupportRail actions={actions} status="Open · closes 20:00" onSelect={route} />

Compose SupportRail in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.

SupportRail props

SupportRail

PropTypeDefaultDescription
...htmlAttributesHTMLAttributes—Standard <div> attributes (aria-*, data-*, id, ...) forwarded to the rail root.
actions*SupportAction[][]The rows, in order. Each renders a button carrying its label, optional description and optional meta.
titlestring'Talk to us'Panel heading, and the panel's accessible name.
statusstring—Small mono line under the heading - opening hours, queue depth, a shift note.
side'right' | 'left''right'Container edge the rail pins to. Flips the tab, the morph origin and the panel's border.
openboolean—Controlled open state. Omit to stay uncontrolled.
defaultOpenbooleanfalseInitial state when uncontrolled.
onOpenChange(open: boolean) => void—Fires whenever the open state changes. Pair with `open` for controlled use.
onSelect(id: string, action: SupportAction) => void—Fires when a row commits - gets its `id` and the full action. The rail stays open; render what happens next in `children`.
animationDisableableAnimation—Retune the morph, per direction. `null` turns it off and the rail snaps.
triggerReactNode—What sits inside the edge tab - an icon, a word, an avatar. The rail owns the tab itself: its edge, its ARIA and its morph. Defaults to a chat glyph; `title` names it either way.
childrenReactNode—Arbitrary content under the rows, inside the same scroll region.
footerReactNode—Pinned bottom strip - on-shift avatars, an SLA line, a link out.
classNamestring—Extra class(es) merged onto the rail root.
styleSupportRailStyle—Inline styles merged onto the rail root - the place to retune the `--support-rail-*` properties.

SupportAction

PropTypeDefaultDescription
id*string—Stable identity. Handed back to `onSelect` and used as the React key.
label*string—The visible name - "Live chat", "Book a call".
iconReactNode—Leading glyph. Any node; the widget sizes the box it sits in.
metastring—Trailing metadata - a wait time, a slot, a ticket number. Set in mono.
descriptionstring—Second line under the label. The rail shows it; the fan puts it in the caption.
onSelect() => void—Fires when this action commits, before the widget's own `onSelect`.

SupportRailStyle

PropTypeDefaultDescription
--support-rail-widthstring | number—`--support-rail-width` - Panel width, capped by the container. Default: `318px`.
--support-rail-row-pad-blockstring | number—`--support-rail-row-pad-block` - Row padding on the block axis. Default: `var(--space-3)`.
--support-rail-row-pad-inlinestring | number—`--support-rail-row-pad-inline` - Row padding on the inline axis. Default: `var(--space-4)`.
--support-rail-surfacestring | number—`--support-rail-surface` - The tab, and the panel it morphs into. Default: `var(--bg-subtle)`.
--support-rail-surface-raisedstring | number—`--support-rail-surface-raised` - The tab while hovered. Default: `var(--bg-surface)`.
--support-rail-linestring | number—`--support-rail-line` - The surface's edge. Default: `var(--border-default)`.
--support-rail-line-softstring | number—`--support-rail-line-soft` - Row dividers. Default: `var(--border-subtle)`.
--support-rail-radiusstring | number—`--support-rail-radius` - The tab's outer corners. Default: `var(--radius-2xl)`.

Frequently asked questions

Pass actions - each item renders a row with a label, an optional description line and optional meta text. Selecting one calls onSelect(id, action) and the panel stays open, so you render what happens next - a form, a confirmation - in children rather than the rail navigating away for you.

Escape closes it, so does the visible close button, and so does a press anywhere outside the panel. It folds back into the tab it grew out of.

trigger takes any node - an icon, a word, an avatar - and the rail renders it inside the tab it owns, keeping the edge, the ARIA and the fold-back animation. Leave it out and you get a chat glyph; title names the tab either way.

side is "right" (the default) or "left", and it flips more than which edge the needle sits on - the collapse origin and the panel's border all mirror with it, so a left rail isn't just a right rail nudged over.

Set in Geist & Newsreader — animated by the house engineZyncat UI · Rev 0.11 · MIT · Built by Tabsir Ahammed · Source on GitHub