Skip to content
ShineUI
Install

Toolbar

Start, center, end.

<sui-toolbar>PanelsStable

About

Action bar in three groups: search and filters at the start, a centered group, actions at the end. It paints no ground of its own and takes the one it sits on, above a table or inside a card. One tab stop; arrow keys move between the controls. variant="flush" is the bar across the top of an application shell.

Usage

<sui-toolbar aria-label="Document actions"> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search documents" aria-label="Search documents" size="md" width="220"></sui-icon-field> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-file-export" label="Export"></sui-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New document"></sui-button> </sui-toolbar> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); </script>
import { SuiToolbar } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return ( <SuiToolbar aria-label="Document actions"> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search documents" aria-label="Search documents" size="md" width="220"></sui-icon-field> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-file-export" label="Export"></sui-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New document"></sui-button> </SuiToolbar> ); }
<script setup> import { SuiToolbar } from "@shineui/vue"; import "@shineui/core/shineui.css"; </script> <template> <SuiToolbar aria-label="Document actions"><sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search documents" aria-label="Search documents" size="md" width="220"></sui-icon-field> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-file-export" label="Export"></sui-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New document"></sui-button></SuiToolbar> </template>
import { Component } from "@angular/core"; import { SuiToolbar } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiToolbar], template: `<sui-toolbar aria-label="Document actions"><sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search documents" aria-label="Search documents" size="md" width="220"></sui-icon-field> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-file-export" label="Export"></sui-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New document"></sui-button></sui-toolbar>`, }) export class Example { }

Basic

The default bar paints no ground, border or rounding of its own, so it takes the surface it sits on and its controls line up with the table below. role="toolbar" sits on the host, so name the bar with a native aria-label. The element emits nothing; the controls inside carry their own events.

<sui-toolbar aria-label="Document actions"> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search documents" aria-label="Search documents" size="md" width="220"></sui-icon-field> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-file-export" label="Export"></sui-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New document"></sui-button> </sui-toolbar>

Variants

variant is the shape. plain, the default, has no edge. card is a bordered, rounded box on the white surface, the bar that floats on the canvas as its own card. flush squares the corners and keeps one bottom rule, so the bar can run edge to edge across a shell or inside a card.

PLAIN
All documents
CARD
All documents
FLUSH
All documents
<div style="display:flex;flex-direction:column;gap:20px"> <div> <div style="font:600 10px var(--font-sans);letter-spacing:var(--tracking-wider);color:var(--ink-faint);margin-bottom:6px">PLAIN</div> <sui-toolbar aria-label="Plain shape"> <span slot="start" style="font:550 13px var(--font-sans)">All documents</span> <sui-button slot="end" variant="outline" size="md" label="Filter"></sui-button> <sui-button slot="end" variant="accent" size="md" label="New document"></sui-button> </sui-toolbar> </div> <div> <div style="font:600 10px var(--font-sans);letter-spacing:var(--tracking-wider);color:var(--ink-faint);margin-bottom:6px">CARD</div> <sui-toolbar variant="card" aria-label="Card shape"> <span slot="start" style="font:550 13px var(--font-sans)">All documents</span> <sui-button slot="end" variant="outline" size="md" label="Filter"></sui-button> <sui-button slot="end" variant="accent" size="md" label="New document"></sui-button> </sui-toolbar> </div> <div> <div style="font:600 10px var(--font-sans);letter-spacing:var(--tracking-wider);color:var(--ink-faint);margin-bottom:6px">FLUSH</div> <sui-toolbar variant="flush" aria-label="Flush shape"> <span slot="start" style="font:550 13px var(--font-sans)">All documents</span> <sui-button slot="end" variant="outline" size="md" label="Filter"></sui-button> <sui-button slot="end" variant="accent" size="md" label="New document"></sui-button> </sui-toolbar> </div> </div>

Tones

tone is the ground. Unset, the bar is transparent and shows the canvas. surface is the white of the cards, sunken the well of a table header, canvas the app background, which only shows when the bar is sticky and content scrolls under it. brand is the navy chrome, shown below.

NO TONE
All documents
SURFACE
All documents
SUNKEN
All documents
<div style="display:flex;flex-direction:column;gap:20px"> <div> <div style="font:600 10px var(--font-sans);letter-spacing:var(--tracking-wider);color:var(--ink-faint);margin-bottom:6px">NO TONE</div> <sui-toolbar variant="flush" aria-label="No tone"> <span slot="start" style="font:550 13px var(--font-sans)">All documents</span> <sui-button slot="end" variant="outline" size="md" label="Filter"></sui-button> <sui-button slot="end" variant="accent" size="md" label="New document"></sui-button> </sui-toolbar> </div> <div> <div style="font:600 10px var(--font-sans);letter-spacing:var(--tracking-wider);color:var(--ink-faint);margin-bottom:6px">SURFACE</div> <sui-toolbar variant="flush" tone="surface" aria-label="Surface tone"> <span slot="start" style="font:550 13px var(--font-sans)">All documents</span> <sui-button slot="end" variant="outline" size="md" label="Filter"></sui-button> <sui-button slot="end" variant="accent" size="md" label="New document"></sui-button> </sui-toolbar> </div> <div> <div style="font:600 10px var(--font-sans);letter-spacing:var(--tracking-wider);color:var(--ink-faint);margin-bottom:6px">SUNKEN</div> <sui-toolbar variant="flush" tone="sunken" aria-label="Sunken tone"> <span slot="start" style="font:550 13px var(--font-sans)">All documents</span> <sui-button slot="end" variant="outline" size="md" label="Filter"></sui-button> <sui-button slot="end" variant="accent" size="md" label="New document"></sui-button> </sui-toolbar> </div> </div>

Three groups

The center group takes the space left over, so a count or a view switch sits in the middle while the ends keep their size.

24 of 120 files
<sui-toolbar aria-label="Library actions"> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search files" aria-label="Search files" size="md" width="180"></sui-icon-field> <span slot="center" style="font:550 13px var(--font-sans);color:var(--ink-muted)">24 of 120 files</span> <sui-button slot="end" variant="ghost" size="md" icon="fa-solid fa-arrows-rotate" label="Refresh"></sui-button> <sui-button slot="end" variant="brand" size="md" label="Upload"></sui-button> </sui-toolbar>

Application shell

A flush rail and a flush bar on one canvas, each with one rule and no ground of its own, so the chrome reads as one piece and only the shell is rounded. landmark="region" makes the bar a named landmark for a shell embedded in a page, as here; landmark="banner" is for the one bar at the top of a whole page.

Projects
Four projects are open. The bar above runs from the rail to the right edge of the shell.
<div style="display:flex;align-items:stretch;border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;background:var(--canvas)"> <sui-side-nav id="tb-shell-nav" variant="flush" aria-label="Primary, Application shell" height="300" active="Projects"></sui-side-nav> <div style="flex:1;min-width:0;display:flex;flex-direction:column"> <sui-toolbar variant="flush" landmark="region" aria-label="Workspace"> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search" aria-label="Search the workspace" size="md" width="200"></sui-icon-field> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-file-export" label="Export"></sui-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New project"></sui-button> </sui-toolbar> <div style="padding:16px 18px;font:460 13px/1.7 var(--font-sans);color:var(--ink-muted);flex:1"> <b style="color:var(--ink);font-size:15px">Projects</b> <div style="margin-top:6px">Four projects are open. The bar above runs from the rail to the right edge of the shell.</div> </div> </div> </div>
// setup(root): properties and events setup(root) { root.querySelector('#tb-shell-nav').sections = shellSections(); }

On navy chrome

tone="brand" paints the bar on the navy ground sui-side-nav paints under the same name, with inverse ink and an inverse rule. Quiet controls take their own inverse tone: the icon buttons here, and any quiet field with tone="inverse", like this select. An accent button reads on navy without one.

Northwind
The bar carries the same navy as the rail, so the two read as one piece of chrome.
<div style="border-radius:var(--radius-lg);overflow:hidden"> <sui-toolbar variant="flush" tone="brand" landmark="region" aria-label="Northwind workspace"> <b slot="start" style="font:730 14.5px var(--font-sans)">Northwind</b> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search" aria-label="Search the workspace" size="md" width="190"></sui-icon-field> <sui-select id="tb-navy-period" slot="start" variant="quiet" tone="inverse" size="md" aria-label="Period"></sui-select> <sui-icon-button slot="end" icon="fa-regular fa-bell" tone="inverse" size="md" aria-label="Notifications"></sui-icon-button> <sui-icon-button slot="end" icon="fa-regular fa-circle-question" tone="inverse" size="md" aria-label="Help"></sui-icon-button> <sui-button slot="end" variant="accent" size="md" icon="fa-solid fa-plus" label="New project"></sui-button> </sui-toolbar> <div style="padding:16px 18px;font:460 13px/1.7 var(--font-sans);color:var(--ink-muted);flex:1;background:var(--surface)">The bar carries the same navy as the rail, so the two read as one piece of chrome.</div> </div>
// setup(root): properties and events setup(root) { const period = root.querySelector('#tb-navy-period'); period.options = ['This month', 'Last month', 'This quarter']; period.value = 'This month'; }

Inside a card

A plain bar inside a card takes the card's white. For a filter strip that reads as part of the table, flush with tone="sunken" gives the well of the table header, running to both edges of the card, so the card keeps one outer border.

14 documents, 3 of them shared.
<sui-card flush heading="Open documents"> <sui-toolbar variant="flush" tone="sunken" aria-label="Document filters"> <sui-button slot="start" variant="ghost" size="sm" icon="fa-solid fa-filter" label="Filter"></sui-button> <sui-button slot="start" variant="ghost" size="sm" icon="fa-solid fa-arrow-down-short-wide" label="Sort"></sui-button> <sui-button slot="end" variant="outline" size="sm" label="Export"></sui-button> </sui-toolbar> <div style="padding:12px 18px;font-size:13px;color:var(--ink-muted)">14 documents, 3 of them shared.</div> </sui-card>

Sticky and elevated

sticky pins the bar to the top of its scroll container; give it a tone, or the content scrolls through it. elevated adds the shadow that says the content is passing underneath, which a bottom rule alone does not say. Scroll the panel to see both.

Quarterly report

Revenue held flat across the quarter while headcount grew by six.

Support volume fell for the third month running.

Two regions moved onto the new plan without a migration window.

Storage costs are the only line item above forecast.

The renewal cohort closes in the second week of next month.

Everything else tracked to plan.

<div style="height:220px;overflow:auto;border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface)"> <sui-toolbar variant="flush" tone="surface" sticky elevated aria-label="Report actions"> <span slot="start" style="font:550 13px var(--font-sans)">Quarterly report</span> <sui-button slot="end" variant="outline" size="md" icon="fa-solid fa-print" label="Print"></sui-button> <sui-button slot="end" variant="accent" size="md" label="Share"></sui-button> </sui-toolbar> <div style="padding:14px 18px;font:460 13px/1.9 var(--font-sans);color:var(--ink-muted)"> <p style="margin:0 0 10px">Revenue held flat across the quarter while headcount grew by six.</p> <p style="margin:0 0 10px">Support volume fell for the third month running.</p> <p style="margin:0 0 10px">Two regions moved onto the new plan without a migration window.</p> <p style="margin:0 0 10px">Storage costs are the only line item above forecast.</p> <p style="margin:0 0 10px">The renewal cohort closes in the second week of next month.</p> <p style="margin:0">Everything else tracked to plan.</p> </div> </div>

One tab stop

The bar is a single tab stop for its buttons, links and selects, and it skips disabled ones. A text field keeps its own stop so the arrow keys still edit text. The log below listens to the native focusin, not to a component event.

Open the guide Tab into the bar, then press the arrow keys.
<div style="display:flex;flex-direction:column;gap:10px"> <sui-toolbar id="tb-keys" aria-label="Report actions, keyboard"> <sui-icon-field slot="start" icon="fa-solid fa-magnifying-glass" placeholder="Search" aria-label="Search reports" size="md" width="160"></sui-icon-field> <select slot="start" aria-label="Period" style="height:30px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);font:460 13px var(--font-sans);padding:0 8px"> <option>This quarter</option> <option>Last quarter</option> </select> <a slot="center" href="#toolbar-one-tab-stop" style="font:550 13px var(--font-sans);color:var(--link)">Open the guide</a> <sui-button slot="end" variant="outline" size="md" label="Print"></sui-button> <sui-button slot="end" variant="accent" size="md" label="Send"></sui-button> </sui-toolbar> <span id="tb-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Tab into the bar, then press the arrow keys.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#tb-log'); root.querySelector('#tb-keys').addEventListener('focusin', (e) => { const el = e.target; const name = el.getAttribute('aria-label') || (el.textContent || '').trim() || el.tagName.toLowerCase(); log.textContent = `Focus: ${name}.`; }); }

Keyboard

KeyAction
TabEnters the bar at its current control, and leaves it on the next press. A text field inside keeps its own tab stop.
ArrowRightMoves focus to the next button, link or select, wrapping to the first. Under dir="rtl" the pair swaps, so the arrow that points along the reading direction is always the forward one.
ArrowLeftMoves focus to the previous button, link or select, wrapping to the last.
HomeMoves focus to the first control in the bar.
EndMoves focus to the last control in the bar.

API

<sui-toolbar>

Properties

PropertyAttributeTypeDefaultDescription
elevatedelevatedbooleanfalseCasts a shadow under the bar, so a sticky bar reads as sitting above the content passing beneath it.
landmarklandmark"banner" | "region" | "toolbar"'toolbar'Role the host carries. The default toolbar announces the bar as one composite widget whose arrow keys move between the controls. banner announces it as the page header instead, for the one bar at the top of a page; a banner inside another landmark is wrong by definition, so a shell embedded in a page takes region, a named landmark of its own. The arrow keys work under all three, but the toolbar role goes with either landmark. There is no label prop: the role sits on the host, so name the bar with a native aria-label, which a landmark needs.
stickystickybooleanfalsePins the bar to the top of its scroll container while the content moves under it. Pair it with a tone.
tonetoneSuiToolbarTone"brand" | "canvas" | "sunken" | "surface" | undefinedGround the bar paints. Leave it unset and the bar is transparent, which is right for a bar in flow; give a sticky bar a ground, or the content scrolls through it. surface is the card of the cards, canvas the app background sui-side-nav paints under the same name, sunken the well of a table header, for a filter strip inside a card. brand is the brand chrome with inverse ink and an inverse rule; quiet controls inside it take their own inverse tone, and an accent button reads on it as it does on the rail. Anything else paints nothing.
variantvariantSuiToolbarVariant"card" | "flush" | "plain"'plain'Shape of the bar, which is to say which edges carry a rule. plain is the default: no border, no rounding and no padding of its own, so the controls line up with whatever sits below the bar. card is a bordered, rounded box on the surface, for a bar that floats on the canvas as its own card. flush squares the corners and keeps a single bottom rule, for the bar across the top of an application shell or a strip that runs edge to edge inside a card. Anything else falls back to plain.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the bar's one tab stop, the control the arrow keys last left it on.

Slots

SlotDescription
centerCentered group.
endRight group (Export, the one accent action).
startLeft group (search field, filters).

Types

SuiToolbarTone type
No members.
SuiToolbarVariant type
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also