FAB

A floating action button carries the primary action of a screen. Use one per screen, for the action people come to that screen to do.

Usage

A FAB shows an icon and no text, so give it a label. It becomes the accessible name and a tooltip, the same way title works on a Button.

<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab label="Add">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>

Sizes

Three sizes, 56, 80 and 96 pixels, the same three an ExtendedFab takes. s is M3's baseline FAB and the default; reach for a bigger one when the FAB is competing with a large hero image. There is no 40 pixel FAB, because M3 Expressive no longer recommends one.

<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab size="s" label="Add, small">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>
<Fab size="m" label="Add, medium">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>
<Fab size="l" label="Add, large">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>

Colors

Six color styles, each named after the tokens it maps to. The three tone styles are the stronger pair and the three container styles the softer one; pick whichever stands out against the surface behind it. There is no surface style, because M3 Expressive no longer recommends one.

<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab variant="primary" label="Primary">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>
<Fab variant="secondary" label="Secondary">
	{#snippet icon()}<Icon>mail</Icon>{/snippet}
</Fab>
<Fab variant="tertiary" label="Tertiary">
	{#snippet icon()}<Icon>favorite</Icon>{/snippet}
</Fab>
<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab variant="primary-container" label="Primary container">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>
<Fab variant="secondary-container" label="Secondary container">
	{#snippet icon()}<Icon>mail</Icon>{/snippet}
</Fab>
<Fab variant="tertiary-container" label="Tertiary container">
	{#snippet icon()}<Icon>favorite</Icon>{/snippet}
</Fab>

Shape

square, the default, is the boxier M3 shape with a radius that grows with the size. round is fully rounded, reading the same way as on Button. A press keeps the shape.

<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab shape="round" label="Round">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>
<Fab shape="square" label="Square">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>

Lowered

lowered drops the shadow a level, for a FAB sitting on a surface that is already raised, such as inside a card or a sheet.

<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab label="Default elevation">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>
<Fab lowered label="Lowered">
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</Fab>

Extended FAB

An extended FAB shows its label as text, so it names itself and needs no tooltip. Use it when the action is not obvious from an icon alone.

<script lang="ts">
	import { ExtendedFab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<ExtendedFab label="Compose">
	{#snippet icon()}<Icon>mail</Icon>{/snippet}
</ExtendedFab>
<ExtendedFab variant="tertiary" label="Search">
	{#snippet icon()}<Icon>search</Icon>{/snippet}
</ExtendedFab>

Collapsing

collapsed shrinks an extended FAB to the icon alone and animates the width. Drive it from whatever you already track, a scroll position or a media query. The label moves to aria-label while it is collapsed, so the button keeps its name.

<script lang="ts">
	import { ExtendedFab, Switch } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	let collapsed = $state(false)
</script>

<label style="display:inline-flex;align-items:center;gap:0.5rem">
	<Switch bind:selected={collapsed} />
	Collapsed
</label>
<ExtendedFab {collapsed} label="Compose">
	{#snippet icon()}<Icon>mail</Icon>{/snippet}
</ExtendedFab>

FAB menu

A FAB menu turns the FAB into two to six related actions, the way it looks on a phone. Open, the FAB turns into a round 56px close button in its top trailing corner, whatever its size, and the items rise into place next to it, lined up with its trailing edge. Close, they fade out. placement decides which side they fan out to, and a short window lets them scroll. Picking an action closes the menu; pass closeOnSelect=false to keep it open for actions that toggle something.

Use FabMenuItem for the actions: it measures like a medium button and always shows its label, ideally with an icon. The menu picks its color set from the FAB's variant, so a tertiary FAB opens a tertiary close button with tertiary container items.

<script lang="ts">
	import { FabMenu, FabMenuItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<div style="display:flex;justify-content:center;padding-block-start:14rem;width:16rem">
	<FabMenu label="Create">
		{#snippet icon()}<Icon>add</Icon>{/snippet}
		<FabMenuItem>
			{#snippet icon()}<Icon>mail</Icon>{/snippet}
			Message
		</FabMenuItem>
		<FabMenuItem>
			{#snippet icon()}<Icon>calendar_today</Icon>{/snippet}
			Event
		</FabMenuItem>
		<FabMenuItem>
			{#snippet icon()}<Icon>videocam</Icon>{/snippet}
			Video
		</FabMenuItem>
	</FabMenu>
</div>
<div style="display:flex;justify-content:center;padding-block-start:14rem;width:16rem">
	<FabMenu label="Create, tertiary" variant="tertiary-container">
		{#snippet icon()}<Icon>add</Icon>{/snippet}
		<FabMenuItem>
			{#snippet icon()}<Icon>mail</Icon>{/snippet}
			Message
		</FabMenuItem>
		<FabMenuItem>
			{#snippet icon()}<Icon>calendar_today</Icon>{/snippet}
			Event
		</FabMenuItem>
		<FabMenuItem>
			{#snippet icon()}<Icon>videocam</Icon>{/snippet}
			Video
		</FabMenuItem>
	</FabMenu>
</div>

On the web

On larger windows and desktop web, M3 opens a regular menu from the FAB instead, 4px away from it, so it reads like any other menu in the app. That needs no component of its own: anchor a Menu to the FAB.

<script lang="ts">
	import { ExtendedFab, Menu, MenuItem } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	let fab: HTMLButtonElement | HTMLAnchorElement | undefined = $state()
</script>

<ExtendedFab
	label="Create"
	style="anchor-name:--create-fab"
	command="toggle-popover"
	commandfor="create-menu"
	aria-haspopup="menu"
	bind:element={fab}
>
	{#snippet icon()}<Icon>add</Icon>{/snippet}
</ExtendedFab>
<Menu
	anchor={fab}
	id="create-menu"
	style="position-anchor:--create-fab"
	--np-menu-margin="4px"
	--np-menu-position-area="bottom span-right"
	--np-menu-justify-self="none"
>
	<MenuItem>Event</MenuItem>
	<MenuItem>Task</MenuItem>
	<MenuItem>Out of office</MenuItem>
	<MenuItem>Working location</MenuItem>
</Menu>

Give a FAB an href and it renders an <a>.

<script lang="ts">
	import { Fab } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Fab href="/components/button" label="Go to buttons">
	{#snippet icon()}<Icon>arrow_forward</Icon>{/snippet}
</Fab>

Theming

Custom propertyDescription
--np-fab-container-colorBackground, overriding the variant.
--np-fab-icon-colorIcon and label color, overriding the variant.
--np-fab-shapeCorner radius at rest, overriding the shape.
--np-fab-elevationShadow, overriding lowered.
--np-fab-motion-spatialHow an extended FAB collapses and expands, by default --np-motion-expressive-default-spatial.

Accessibility

The FAB renders a native <button>, or an <a> with href. It usually shows an icon alone, so the label is what gives it a name, and the same text becomes its tooltip.

An extended FAB shows the label as text and is named by it. Keep the FAB late in the DOM, where it sits visually, so the tab order matches the screen, and remember that one screen has one FAB: two of them leave people guessing which action the screen is about.

A FAB menu is a role="menu", and a menu may only hold menu items; FabMenuItem brings role="menuitem" with it. Focus stays on the close button when the menu opens, and the items follow it top to bottom. They share one tab stop, the arrow keys move between them, and typing a letter jumps to the action that starts with it. On the FAB, ↓ and ↑ open the menu on its first and last action.

API

Fab attributes

A FAB renders a <button>, or an <a> with an href, and takes that element's attributes. bind:element gives you whichever it rendered.

AttributeTypeDefaultDescription
labelstring | undefinedundefinedAccessible name and tooltip text. A FAB shows no text, so set it.
iconSnippet | undefinedundefinedThe icon.
variant'primary' | 'secondary' | 'tertiary' | 'primary-container' | 'secondary-container' | 'tertiary-container''primary-container'Color style, named after the tokens it maps to.
size's' | 'm' | 'l''s'56, 80 or 96 pixels.
shape'round' | 'square''square'Corner radius.
loweredbooleanfalseDrops the shadow a level.

ExtendedFab attributes

Everything Fab takes, with label shown as text rather than used as a tooltip, plus:

AttributeTypeDefaultDescription
collapsedbooleanfalseShrinks to the icon alone, animating the width. The label moves to aria-label.

FabMenuItem attributes

A medium tonal Button with role="menuitem", taking its attributes, plus:

AttributeTypeDefaultDescription
iconSnippet | undefinedundefinedLeading icon, which tells the actions apart at a glance.