A floating toolbar whose tools grow into a field inside it: Link widens the bar into a URL field while the other tools step back, and narrows back when done.
Installation
When to use
- Over a text selection in an editor, or over a selected row: a few tools where the work is, without a menu.
- Tools that need a word or a link: the field opens inside the toolbar, so nothing else covers the text.
When not to use
- A toolbar that's always there at the top of an editor: a fixed bar (Button Group, Toggle Group) is steadier.
- Many tools: past seven or so, a menu or a command palette finds them faster.
Usage
Floating Toolbar with your own data: a whole component you can paste as a page.
"use client"
import { FloatingToolbar } from "@/components/dashral/pro/floating-toolbar"
import { useState } from "react"
import { Bold, Italic, Link2 } from "lucide-react"
export default function SelectionTools() {
const [bold, setBold] = useState(false)
const [italic, setItalic] = useState(false)
const [link, setLink] = useState<string | undefined>()
return (
<FloatingToolbar
tools={[
{ id: "bold", label: "Bold", icon: <Bold />, shortcut: ["mod", "B"], active: bold, onClick: () => setBold(!bold) },
{ id: "italic", label: "Italic", icon: <Italic />, shortcut: ["mod", "I"], active: italic, onClick: () => setItalic(!italic) },
{
id: "link",
label: "Link",
icon: <Link2 />,
active: !!link,
group: "insert",
field: { type: "url", placeholder: "Paste a link", value: link, onSubmit: setLink, onRemove: () => setLink(undefined) },
},
]}
/>
)
}API reference
FloatingToolbar
A floating toolbar: tools along one pill with a gliding highlight and their names above; a tool with a field (Link, Comment) widens the bar into that field, in place, and back when it's applied or left.
| Prop | Type | Default |
|---|---|---|
tools | ToolbarTool[]Required. | — |
open | booleanShown or not (a selection there or not); it fades and settles in place. | true |
label | stringNamed for screen readers; "Formatting" by default. | "Formatting" |
fieldWidth | numberA field's width, in px (never wider than the screen). | 320 |
onEscape | () => voidEscape on the tools (not in a field): close it, put the focus back in the text. | — |
className | string | — |
classNames | FloatingToolbarClassNames | — |
type ToolbarField
| Prop | Type | Default |
|---|---|---|
placeholder | string | — |
value | stringWhat it starts with (the link already there). | — |
type | "text" | "url" | — |
submitLabel | stringThe button that applies it; a check by default. | — |
onSubmit | (value: string) => void | — |
onRemove | () => voidAdds a remove button (unlink, delete the comment). | — |
type ToolbarTool
| Prop | Type | Default |
|---|---|---|
id | string | — |
label | string | — |
icon | React.ReactNode | — |
shortcut | string[]Shown in its name above it (["mod", "B"]). Shown only: wire the keys in your editor. | — |
active | booleanOn (bold already applied): pressed, and drawn so. | — |
onClick | () => void | — |
field | ToolbarFieldOr a field it grows into, inside the toolbar. | — |
group | stringTools of the same group sit together; a line separates groups. | — |
disabled | boolean | — |
type FloatingToolbarClassNames
| Prop | Type | Default |
|---|---|---|
root | string | — |
bar | string | — |
Keyboard
| Keys | Action |
|---|---|
| Tab | Reach the toolbar (one stop) |
| ←or→ | Along the tools |
| HomeorEnd | The first or last tool |
| EnterorSpace | Use the tool, or open its field |
| Enter | In a field: apply it, back to the tools |
| Esc | In a field: back to its tool; on the tools: onEscape (close, back to the text) |
Accessibility
- A toolbar (role toolbar, horizontal) named by label, with one tab stop and roving focus; each tool is named, a toggle reports aria-pressed, a tool with a field says it opens one.
- A field is a group named after its tool; its input, Apply, Remove and Back to the tools are all named, and the focus returns to the tool after.
- A tool's name and keys show above it on hover or focus; its keys are in aria-keyshortcuts.
Motion
- It appears settling up 6px with a fade, and leaves the same way.
- Opening a field, the tools step back (a fade, to 97%, a 2px blur) while the bar's width tweens to the field's (0.38 s) and the field slides in; closing, the reverse.
- One highlight glides along the tools, and the name above follows it. Smooth tweens on [0.4, 0, 0.2, 1], never springs, except the small press on Apply. With reduced motion it only fades.