{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toolbar",
  "title": "Toolbar",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A frosted macOS toolbar with a title block, icon and labeled buttons, segmented view switches, an expanding search field, separators and spacers, built on Radix Toolbar so the whole bar is one tab stop.",
  "dependencies": [
    "radix-ui",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/toolbar.tsx",
      "content": "// Ballmac UI: Toolbar. https://ui.ballmac.com/components/toolbar\n\"use client\"\n\nimport * as React from \"react\"\nimport { Search, X } from \"lucide-react\"\nimport { Toolbar as Primitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useMessages } from \"@/lib/ballmac/i18n\"\n\ntype ToolbarProps = Omit<React.ComponentProps<typeof Primitive.Root>, \"title\"> & {\n  /** Accessible name of the toolbar. */\n  label: string\n  /** Large title at the leading edge. */\n  title?: React.ReactNode\n  /** Smaller line under the title, such as an item count. */\n  subtitle?: React.ReactNode\n}\n\n/**\n * A macOS-style toolbar: translucent, with an optional title block and groups of buttons.\n * Built on Radix Toolbar, so Left and Right arrows move between buttons and the whole bar is one Tab stop.\n */\nfunction Toolbar({ label, title, subtitle, className, children, ...props }: ToolbarProps) {\n  return (\n    <Primitive.Root\n      data-slot=\"toolbar\"\n      aria-label={label}\n      className={cn(\"flex min-h-[52px] w-full items-center gap-1.5 border-b border-foreground/[0.08] bg-background/70 px-3 text-foreground backdrop-blur-xl backdrop-saturate-150 [scrollbar-width:none] max-sm:overflow-x-auto [&::-webkit-scrollbar]:hidden\", className)}\n      {...props}\n    >\n      {(title || subtitle) && (\n        <div className=\"me-3 grid min-w-0 shrink-0 leading-tight\">\n          {title && <span className=\"truncate text-[15px] font-semibold\">{title}</span>}\n          {subtitle && <span className=\"truncate text-[11px] text-muted-foreground\">{subtitle}</span>}\n        </div>\n      )}\n      {children}\n    </Primitive.Root>\n  )\n}\n\ntype ToolbarButtonProps = React.ComponentProps<typeof Primitive.Button> & {\n  /** Icon. */\n  icon?: React.ReactNode\n  /** Show the text label under the icon (the classic macOS toolbar item) instead of icon only. */\n  labeled?: boolean\n  /** Shown as a pressed (toggled on) button. */\n  pressed?: boolean\n}\n\n/** An icon button. Icon-only buttons need an aria-label; labeled ones use their text. */\nfunction ToolbarButton({ icon, labeled = false, pressed, className, children, ...props }: ToolbarButtonProps) {\n  return (\n    <Primitive.Button\n      data-slot=\"toolbar-button\"\n      aria-pressed={pressed}\n      title={props[\"aria-label\"]}\n      className={cn(\n        \"inline-flex shrink-0 items-center justify-center rounded-md text-foreground/75 outline-none transition-colors duration-100 hover:bg-foreground/[0.07] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-40 aria-pressed:bg-foreground/[0.1] aria-pressed:text-foreground [&_svg]:size-[18px] [&_svg]:shrink-0\",\n        labeled ? \"min-w-14 flex-col gap-0.5 px-2 py-1 text-[10px]\" : \"h-8 min-w-8 gap-1.5 px-1.5 text-[13px]\",\n        className\n      )}\n      {...props}\n    >\n      {icon}\n      {children}\n    </Primitive.Button>\n  )\n}\n\nfunction ToolbarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"toolbar-group\" role=\"group\" className={cn(\"flex items-center gap-0.5\", className)} {...props} />\n}\n\ntype ToolbarSegmentedProps = React.ComponentProps<typeof Primitive.ToggleGroup>\n\n/** A single-choice segmented control, such as icon / list / column view. Use ToolbarSegment for the choices. */\nfunction ToolbarSegmented({ className, ...props }: ToolbarSegmentedProps) {\n  return (\n    <Primitive.ToggleGroup\n      data-slot=\"toolbar-segmented\"\n      className={cn(\"inline-flex items-center gap-px rounded-lg bg-foreground/[0.06] p-0.5\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ToolbarSegment({ className, ...props }: React.ComponentProps<typeof Primitive.ToggleItem>) {\n  return (\n    <Primitive.ToggleItem\n      data-slot=\"toolbar-segment\"\n      className={cn(\n        \"inline-flex h-6 min-w-8 items-center justify-center rounded-md px-1.5 text-foreground/70 outline-none transition-[background-color,color,box-shadow] duration-100 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-[0_0.5px_1.5px_rgb(0_0_0/0.25)] dark:data-[state=on]:bg-foreground/[0.18] [&_svg]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ToolbarSeparator({ className, ...props }: React.ComponentProps<typeof Primitive.Separator>) {\n  return <Primitive.Separator data-slot=\"toolbar-separator\" className={cn(\"mx-1.5 h-5 w-px bg-foreground/15\", className)} {...props} />\n}\n\n/** Pushes everything after it to the trailing edge. */\nfunction ToolbarSpacer({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"toolbar-spacer\" aria-hidden=\"true\" className={cn(\"flex-1\", className)} {...props} />\n}\n\ntype ToolbarSearchProps = Omit<React.ComponentProps<\"input\">, \"type\"> & {\n  /** Width when collapsed, as a CSS length. */\n  collapsedWidth?: string\n  /** Width when focused or filled. */\n  expandedWidth?: string\n  /** Called when the clear button is pressed. */\n  onClear?: () => void\n}\n\n/** A search field that widens when focused, with a clear button. */\nfunction ToolbarSearch({ collapsedWidth = \"9rem\", expandedWidth = \"15rem\", className, value, onClear, \"aria-label\": ariaLabel, ...props }: ToolbarSearchProps) {\n  const msg = useMessages()\n  ariaLabel ??= msg(\"toolbar.ariaLabel\", \"Search\")\n  const filled = typeof value === \"string\" ? value.length > 0 : false\n  return (\n    <div\n      data-slot=\"toolbar-search\"\n      className=\"group/search relative w-(--collapsed) shrink-0 transition-[width] duration-200 ease-out focus-within:w-(--expanded) motion-reduce:transition-none\"\n      style={{ [\"--collapsed\" as string]: collapsedWidth, [\"--expanded\" as string]: expandedWidth }}\n    >\n      <Search aria-hidden=\"true\" className=\"pointer-events-none absolute top-1/2 start-2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n      <input\n        type=\"search\"\n        value={value}\n        aria-label={ariaLabel}\n        placeholder={msg(\"toolbar.search\", \"Search\")}\n        className={cn(\n          \"h-7 w-full rounded-md border border-foreground/10 bg-foreground/[0.05] pe-7 ps-7 text-[13px] outline-none transition-[box-shadow,background-color] placeholder:text-muted-foreground focus:bg-background focus-visible:ring-[3px] focus-visible:ring-ring/50 [&::-webkit-search-cancel-button]:hidden\",\n          className\n        )}\n        {...props}\n      />\n      {filled && onClear && (\n        <button\n          type=\"button\"\n          aria-label={msg(\"toolbar.clearSearch\", \"Clear search\")}\n          onClick={onClear}\n          className=\"absolute top-1/2 end-1.5 flex size-4 -translate-y-1/2 items-center justify-center rounded-full bg-foreground/30 text-background outline-none hover:bg-foreground/50 focus-visible:ring-2 focus-visible:ring-ring\"\n        >\n          <X aria-hidden=\"true\" className=\"size-2.5\" strokeWidth={3} />\n        </button>\n      )}\n    </div>\n  )\n}\n\nexport {\n  Toolbar,\n  ToolbarButton,\n  ToolbarGroup,\n  ToolbarSegmented,\n  ToolbarSegment,\n  ToolbarSeparator,\n  ToolbarSpacer,\n  ToolbarSearch,\n  type ToolbarProps,\n  type ToolbarButtonProps,\n  type ToolbarSearchProps,\n}\n",
      "type": "registry:ui",
      "target": "@components/ballmac/toolbar.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "toolbar",
      "macos",
      "segmented",
      "buttons",
      "search"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "<Toolbar label title subtitle> with ToolbarButton (icon, labeled, pressed), ToolbarGroup, ToolbarSegmented + ToolbarSegment (single choice), ToolbarSeparator, ToolbarSpacer and ToolbarSearch. Arrow keys move between buttons.",
      "whenToUse": [
        "Document and file-manager windows",
        "Editors with many commands"
      ],
      "whenNotToUse": [
        "Page navigation (navbar)",
        "One or two buttons (button-group)"
      ],
      "composesWith": [
        "mac-window",
        "finder-window",
        "button-group"
      ],
      "a11y": [
        {
          "keys": "Tab",
          "action": "Enters and leaves the toolbar as a single stop"
        },
        {
          "keys": "ArrowLeft / ArrowRight",
          "action": "Moves between buttons; Home and End jump to the ends"
        },
        {
          "keys": "Screen readers",
          "action": "Named by its label; pressed buttons use aria-pressed"
        }
      ],
      "customization": [
        "labeled buttons",
        "segmented groups",
        "ToolbarSearch widths"
      ]
    },
    "examples": [
      "toolbar-demo",
      "toolbar-labeled"
    ],
    "url": "https://ui.ballmac.com/components/toolbar"
  },
  "categories": [
    "macos"
  ],
  "type": "registry:ui"
}