All components
Action Bar
A floating bulk-action toolbar that appears above the page when items are selected.
1234567891011121314151617181920import { ActionBar } from "#components/ui/base-ui/action-bar.tsx";import { Button } from "#components/ui/base-ui/button.tsx";
export function Example({ selection }: { selection: string[] }) { if (selection.length === 0) return null; return ( // the positioner fixes the bar over the viewport bottom, above the page — // render it only while a selection exists; parts compose via render <ActionBar.Positioner> <ActionBar.Content> <ActionBar.SelectionTrigger> {selection.length} selected </ActionBar.SelectionTrigger> <ActionBar.Separator /> <Button variant="ghost" size="xs">Download</Button> <Button variant="ghost" size="xs">Delete</Button> </ActionBar.Content> </ActionBar.Positioner> );}Install
1npx stylexblocks add action-barSource
components/ui/base-ui/action-bar.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120import * as stylex from "@stylexjs/stylex";import { createSlotRecipeContext } from "#lib/base-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { zIndices } from "@stylexblocks/ui/tokens/z-indices.stylex.ts";import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { semanticShadows } from '@stylexblocks/ui/semantic-tokens/shadows.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const actionBarPositioner = stylex.create({ base: { position: 'fixed', display: 'flex', justifyContent: 'center', pointerEvents: 'none', insetInline: 0, top: 'unset', bottom: 'calc(env(safe-area-inset-bottom) + 20px)', },})
const actionBarContent = stylex.create({ base: { backgroundColor: semanticColors.bgPanel, boxShadow: semanticShadows.md, display: 'flex', alignItems: 'center', gap: spacing._3, borderRadius: semanticRadii.panel, paddingBlock: spacing._2_5, paddingInline: spacing._3, pointerEvents: 'auto', translate: 'calc(-1 * var(--scrollbar-width) / 2) 0px', },})
const actionBarSeparator = stylex.create({ base: { width: '1px', height: sizes._5, backgroundColor: semanticColors.border, },})
const actionBarSelectionTrigger = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', gap: spacing._2, alignSelf: 'stretch', fontSize: fontSizes.sm, lineHeight: lineHeights.sm, paddingInline: spacing._4, paddingBlock: spacing._1, borderRadius: semanticRadii.control, borderWidth: '1px', borderStyle: 'dashed', },})
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axisconst actionBarRecipe = defineSlotRecipe({ slots: { positioner: { styles: actionBarPositioner }, content: { styles: actionBarContent }, separator: { styles: actionBarSeparator }, selectionTrigger: { styles: actionBarSelectionTrigger }, }, defaultVariants: {},});
// Local chrome the recipe leaves to the composition layer: a z-index so the// fixed bar floats above sticky page chrome, a hairline on the panel, and// the selection chip's dashed border color.const chrome = stylex.create({ positioner: { zIndex: zIndices.dropdown, }, content: { borderWidth: "1px", borderStyle: "solid", borderColor: semanticColors.border, }, selectionTrigger: { borderColor: semanticColors.border, color: semanticColors.fgMuted, },});
/** * Action Bar — a floating bulk-action toolbar composed from presentational * parts: the positioner fixes itself over the viewport bottom (render it * only while a selection exists), the content is the floating panel. Every * part composes via `render` through Base UI's `useRender` primitives. */const { withProvider, withContext } = createSlotRecipeContext(actionBarRecipe);
export const ActionBar = { Positioner: withProvider( "div", () => [actionBarPositioner.base, chrome.positioner], "ActionBarPositioner", ), Content: withContext( "div", () => [actionBarContent.base, chrome.content], "ActionBarContent", ), SelectionTrigger: withContext( "span", () => [actionBarSelectionTrigger.base, chrome.selectionTrigger], "ActionBarSelectionTrigger", ), Separator: withContext("div", "separator", "ActionBarSeparator"),};