Floating tabs.
A pill-shaped track holding two to five mutually exclusive tabs. It switches the view of content already on screen — it never navigates away and never submits anything. One tab is always active.
Anatomy
Four parts. The track is the only element with a fill at rest — every tab is transparent until it becomes active.
The container. Fully rounded, #152F1A, hugs its contents in both directions. 52px tall.
One option. Transparent when inactive, a filled pill at #E1FFAD 12% when active.
20 × 20, filled. Active tab only — it is the second signal for the active state.
Required. Label medium, 14 / 20, weight 600. One or two words, no truncation.
Spacing
Every measurement the track and its tabs use. Active = Overview, so both the 12px active edge and the 16px inactive edge are visible at once.
Hover or focus a row to see that measurement on the component above.
8px --sprout-spacing-xsTrack padding, verticalFixed in every state. 8 + 36 tab + 8 = the 52px track height.12px --sprout-spacing-sTrack padding, side — active edgeWhen the tab on that edge is active. The pill carries the last 4px of its own padding, so the two together read as one consistent margin.16px --sprout-spacing-mTrack padding, side — inactive edgeWhen the tab on that edge is inactive.16px --sprout-spacing-mBetween tabsAuto-layout gap on the track. Every pair, active or not.8px --sprout-spacing-xsTab padding8px all round. Identical on active and inactive tabs.4px --sprout-spacing-2xsIcon to labelInside the active tab.Variants
Three content variants. Pick one per control and keep it consistent across every tab. Width behaviour is hug only.
The icon appears on the active tab only. This is the pattern that ships in the library.
Use when the options are not easily iconified — date ranges, statuses, numbers.
Two or three tabs maximum, and only for universally understood pairs (grid/list, map/table). Every tab needs an accessible name and a tooltip.
Width — hug only in v1. Each tab sizes to its label and the track hugs the tabs, so the control is always as wide as its content and no wider. There is no fill or stretch behaviour yet.
Styles
One treatment. brandSurface tokens carry the same value in light and dark mode, so the component never inverts — the two panels below are the same component, unchanged.
The app surface. The pill reads as a control against it, not as content.
Same fills, same labels, same icon. Nothing inverts — this is the point of using brandSurface.
Floating tabs use brandSurface colour tokens, and those tokens hold the same value in light and dark mode. The component does not invert or flip — it looks identical in both. Do not build a light-mode variant.
These three values are not variables yet — there is no brandSurface collection in this file, and nothing on the canvas is bound to one. Until it exists, the component carries raw hex.
States
Active and inactive tabs each get their own hover and pressed treatment, so a tab is never ambiguous about whether it is already the current view.
no fill
label brightens, no fill
white 6% overlay
2px ring, outside
label at 40%
#E1FFAD 12%
fill one step lighter
ring outside the pill
Size
One size in v1: 52px tall. The active state changes colour and adds a filled icon — it never changes the type size or weight.
label medium 14 / 20 · icon 20 filled · tab padding 8 · track padding 8 / 16 (12 on the active edge) · gap 16
On the question of text height and padding: leave the type as it is. 14 / 20 is Sprout's label medium, and raising the line height only makes the pill taller without helping legibility. The height comes from padding instead: 20px label + 8px tab padding = a 36px tab, + 8px track padding = the 52px you want.
The thing that does need solving is the touch target. The track is 52px, but each individual tab is only 36px tall — still under the 44px minimum for touch. Fix it in code with a transparent hit area that fills the full 52px track height, not with more padding. Adding padding to reach 44px would push the track to 60px and break the size you have chosen.
Icons
The icon appears on the active tab only, and is always filled. It is the second signal for the active state, alongside colour.
Because the icon only appears when active, the track changes width as the user moves through it. Give the track a min-width equal to its widest state, or use Fill, so the layout around it does not shift.
Do & Don't
The four mistakes this component actually attracts in review.
Usage
Reach for floating tabs when the user is choosing how to look at something, not what to do with it. v1 is for the mobile app.
v1 targets the mobile app. Floating tabs sit above the content, over the scroll, on a phone screen. Desktop and back-office placement is out of scope until the pattern has shipped on mobile.
- Two to five options, all worth showing at once
- The options are mutually exclusive and one is always active
- Switching is instant and reversible — no save, no confirm
- The content being switched is already on screen
- Typical cases: view switch (grid / list / map), time range (day / week / month), status filter on a work-order list
- Six or more options → Tabs, or a Select
- Options are page-level destinations → Navigation
- Multiple options can be on at once → Chips or checkboxes
- The choice is part of a form submission → Radio group
- The tab performs an action → Button or Button group
- It is a single on/off → Switch
Bottom spacing for floating elements
Floating tabs and the FAB sit on top of the scroll. Every scrollable area — Overview, Time, Rows, Files — must reserve bottom padding for whatever floats over it, or the last row of content hides behind the pill.
FAB height is not confirmed yet — fill it in before this ships to a product.
The plain case — tabs over scrolling content. Note what this screen shows: "Mark as ready" is cut off behind the pill, because the scroll area has no bottom padding. Exactly the bug the caution above describes.
The tabs share the screen with a floating action button. The FAB keeps its own corner and the track stays centred, so neither blocks the other.
Related — Tabs · Buttons · Chips · Navigation · Ribbon toolbar