component
InlineBanner
A filled status pill with an icon and a label, in four statuses and two sizes.
import { InlineBanner } from 'rost/components/inline-banner/InlineBanner'
<InlineBanner status="positive">Saved just now</InlineBanner>
<InlineBanner status="negative" size="small">Payment failed</InlineBanner>src/components/inline-banner/InlineBanner.tsx
import type { HTMLAttributes, ReactNode } from 'react'
import './inline-banner.css'
import plusSmall from './icons/plus-small.svg?url'
import plusMedium from './icons/plus-medium.svg?url'
import alarmSmall from './icons/alarm-small.svg?url'
import alarmMedium from './icons/alarm-medium.svg?url'
import positiveSmall from './icons/positive-small.svg?url'
import positiveMedium from './icons/positive-medium.svg?url'
import warningSmall from './icons/warning-small.svg?url'
import warningMedium from './icons/warning-medium.svg?url'
export type InlineBannerStatus = 'default' | 'negative' | 'positive' | 'warning'
export type InlineBannerSize = 'small' | 'medium'
export interface InlineBannerProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
readonly status?: InlineBannerStatus
readonly size?: InlineBannerSize
/** Default status only: replaces the plus, as Figma's slot does. */
readonly icon?: ReactNode
/** Default status only: hides the glyph and leaves just the label. */
readonly showSlot?: boolean
/** The label. */
readonly children: ReactNode
}
// The exact icons Figma exports for each status and size.
const GLYPH: Record<InlineBannerStatus, { part: string; small: string; medium: string }> = {
default: { part: 'icons-plus', small: plusSmall, medium: plusMedium },
negative: { part: 'icons-alarm', small: alarmSmall, medium: alarmMedium },
positive: { part: 'icons-positive', small: positiveSmall, medium: positiveMedium },
warning: { part: 'icons-warning', small: warningSmall, medium: warningMedium },
}
const b = 'rost-inline-banner'
/**
* A filled status pill: a glyph and a short label. Built from
* contracts/inline-banner.contract.yaml, following the Figma layers, so the
* generated CSS lands on the same parts. It's spans all the way down and
* sits inline, so it can go inside a sentence.
*
* Pass `role="status"` (or `role="alert"` for negative) when it appears
* because of something the person just did.
*/
export function InlineBanner({ status = 'default', size = 'small', icon, showSlot = true, children, className, ...rest }: InlineBannerProps) {
const g = GLYPH[status]
const glyph = (
<span className={`${b}__${g.part}`} aria-hidden="true">
<img src={g[size]} alt="" />
</span>
)
return (
<span className={[b, `${b}--status-${status}`, `${b}--size-${size}`, className].filter(Boolean).join(' ')} {...rest}>
<span className={`${b}__background`} />
{status === 'default'
? showSlot && <span className={`${b}__slot`}>{icon ?? glyph}</span>
: glyph}
<span className={`${b}__label`}>
<span className={`${b}__text`}>{children}</span>
</span>
</span>
)
}When to use
- Telling people something about the page or a section they should notice: saved, offline, open for work, needs your attention.
Smallin a line of text,Mediumon its own line or at the top of a section.
When not to use
- The status of one item in a list or table. That's Badge, smaller and quieter.
- Anything that needs a decision. That's a dialog. A banner tells, it doesn't ask.
Behaviour
status (Default, Positive, Negative, Warning) sets the fill and the icon: plus, check, alarm, warning. You can't interact with it: no hover, no focus.
Accessibility
- Element: a
<span>or a<p>. If it shows up because of something the person just did, put it in a live region (role="status", orrole="alert"forNegative). - Colour: the icon carries the status as well as the fill. akount.ing still shows no icon (that's its deviation), so there the words have to say the status.
- Glyph:
aria-hidden="true". The label carries the meaning.
Content
One short sentence, no full stop if it's a fragment. Lead with what happened: "Saved just now", "Payment failed: card expired".
Props
| Prop | Type | Values | Default |
|---|---|---|---|
| status | enum | Default · Negative · Positive · Warning | Default |
| size | enum | Medium · Small | Small |
| label | slot | ||
| icon | slot | ||
| showSlot | boolean | true |
Where it's used
- akount.ing
src/rost/inline-banner/InlineBanner.tsx.rost-inline-banner
Values
Straight from the contract. Each value sits next to its token, because six tokens here all come out as #3a3a3a and the colour alone can't tell you which one is right.
Never changes
| Part | Property | Value | Token |
|---|---|---|---|
| root | display | inline-flex | layout |
| root | vertical-align | middle | layout |
| root | align-items | center | layout |
| root | position | relative | layout |
| root | padding | 8px | spacing/gap/small |
| root | border-radius | 8px | spacing/radius/small |
| background | position | absolute | layout |
| background | inset | 0 | layout |
| background | background | #3a3a3a | non-interactive/background/tertiary |
| background | opacity | 0.32 | opacity/mild |
| background | border-radius | 8px | spacing/radius/small |
| label | overflow | clip | layout |
| label | justify-content | center | layout |
| label | align-items | center | layout |
| label | display | flex | layout |
| label | flex-shrink | 0 | layout |
| label | position | relative | layout |
| text | flex-shrink | 0 | layout |
| text | white-space | nowrap | layout |
| text | text-align | center | layout |
| text | position | relative | layout |
| text | font-family | 'General Sans Variable' | font-family/base |
| text | color | #d3d9d5 | non-interactive/foreground/secondary |
Changes by variant
icons-alarm.flex-shrink2
| status=Negative, size=Small | 0 | layout |
| status=Negative, size=Medium | 0 | layout |
icons-alarm.height2
| status=Negative, size=Small | 16px | spacing/size/2xsmall |
| status=Negative, size=Medium | 20px | spacing/size/xsmall |
icons-alarm.position2
| status=Negative, size=Small | relative | layout |
| status=Negative, size=Medium | relative | layout |
icons-alarm.width2
| status=Negative, size=Small | 16px | spacing/size/2xsmall |
| status=Negative, size=Medium | 20px | spacing/size/xsmall |
icons-plus.flex-shrink2
| status=Default, size=Small | 0 | layout |
| status=Default, size=Medium | 0 | layout |
icons-plus.height2
| status=Default, size=Small | 16px | spacing/size/2xsmall |
| status=Default, size=Medium | 20px | spacing/size/xsmall |
icons-plus.position2
| status=Default, size=Small | relative | layout |
| status=Default, size=Medium | relative | layout |
icons-plus.width2
| status=Default, size=Small | 16px | spacing/size/2xsmall |
| status=Default, size=Medium | 20px | spacing/size/xsmall |
icons-positive.flex-shrink2
| status=Positive, size=Small | 0 | layout |
| status=Positive, size=Medium | 0 | layout |
icons-positive.height2
| status=Positive, size=Small | 16px | spacing/size/2xsmall |
| status=Positive, size=Medium | 20px | spacing/size/xsmall |
icons-positive.position2
| status=Positive, size=Small | relative | layout |
| status=Positive, size=Medium | relative | layout |
icons-positive.width2
| status=Positive, size=Small | 16px | spacing/size/2xsmall |
| status=Positive, size=Medium | 20px | spacing/size/xsmall |
icons-warning.flex-shrink2
| status=Warning, size=Small | 0 | layout |
| status=Warning, size=Medium | 0 | layout |
icons-warning.height2
| status=Warning, size=Small | 16px | spacing/size/2xsmall |
| status=Warning, size=Medium | 20px | spacing/size/xsmall |
icons-warning.position2
| status=Warning, size=Small | relative | layout |
| status=Warning, size=Medium | relative | layout |
icons-warning.width2
| status=Warning, size=Small | 16px | spacing/size/2xsmall |
| status=Warning, size=Medium | 20px | spacing/size/xsmall |
label.gap4
| status=Default, size=Medium | 8px | spacing/gap/small |
| status=Negative, size=Medium | 8px | spacing/gap/small |
| status=Positive, size=Medium | 8px | spacing/gap/small |
| status=Warning, size=Medium | 8px | spacing/gap/small |
label.padding4
| status=Default, size=Medium | 0px | spacing/gap/none |
| status=Negative, size=Medium | 0px | spacing/gap/none |
| status=Positive, size=Medium | 0px | spacing/gap/none |
| status=Warning, size=Medium | 0px | spacing/gap/none |
label.padding-bottom4
| status=Default, size=Small | 2px | spacing/gap/3xsmall |
| status=Negative, size=Small | 2px | spacing/gap/3xsmall |
| status=Positive, size=Small | 2px | spacing/gap/3xsmall |
| status=Warning, size=Small | 2px | spacing/gap/3xsmall |
root.gap8
| status=Default, size=Small | 4px | spacing/gap/2xsmall |
| status=Negative, size=Small | 4px | spacing/gap/2xsmall |
| status=Positive, size=Small | 4px | spacing/gap/2xsmall |
| status=Warning, size=Small | 4px | spacing/gap/2xsmall |
| status=Default, size=Medium | 8px | spacing/gap/small |
| status=Negative, size=Medium | 8px | spacing/gap/small |
| status=Positive, size=Medium | 8px | spacing/gap/small |
| status=Warning, size=Medium | 8px | spacing/gap/small |
root.opacity6
| status=Negative, size=Small | 1 | opacity/solid |
| status=Positive, size=Small | 1 | opacity/solid |
| status=Warning, size=Small | 1 | opacity/solid |
| status=Negative, size=Medium | 1 | opacity/solid |
| status=Positive, size=Medium | 1 | opacity/solid |
| status=Warning, size=Medium | 1 | opacity/solid |
slot.align-items2
| status=Default, size=Small | center | layout |
| status=Default, size=Medium | center | layout |
slot.display2
| status=Default, size=Small | flex | layout |
| status=Default, size=Medium | flex | layout |
slot.flex-shrink2
| status=Default, size=Small | 0 | layout |
| status=Default, size=Medium | 0 | layout |
slot.gap2
| status=Default, size=Small | 0px | spacing/gap/none |
| status=Default, size=Medium | 0px | spacing/gap/none |
slot.padding2
| status=Default, size=Small | 0px | spacing/gap/none |
| status=Default, size=Medium | 0px | spacing/gap/none |
slot.position2
| status=Default, size=Small | relative | layout |
| status=Default, size=Medium | relative | layout |
text.font-size8
| status=Default, size=Small | 12px | typography/label/font-size |
| status=Negative, size=Small | 12px | typography/label/font-size |
| status=Positive, size=Small | 12px | typography/label/font-size |
| status=Warning, size=Small | 12px | typography/label/font-size |
| status=Default, size=Medium | 16px | typography/body/font-size |
| status=Negative, size=Medium | 16px | typography/body/font-size |
| status=Positive, size=Medium | 16px | typography/body/font-size |
| status=Warning, size=Medium | 16px | typography/body/font-size |
text.font-weight8
| status=Default, size=Small | 500 | typography/label/font-weight/medium |
| status=Negative, size=Small | 500 | typography/label/font-weight/medium |
| status=Positive, size=Small | 500 | typography/label/font-weight/medium |
| status=Warning, size=Small | 500 | typography/label/font-weight/medium |
| status=Default, size=Medium | 500 | typography/body/font-weight/medium |
| status=Negative, size=Medium | 500 | typography/body/font-weight/medium |
| status=Positive, size=Medium | 500 | typography/body/font-weight/medium |
| status=Warning, size=Medium | 500 | typography/body/font-weight/medium |
text.letter-spacing8
| status=Default, size=Small | 0px | typography/label/letter-spacing |
| status=Negative, size=Small | 0px | typography/label/letter-spacing |
| status=Positive, size=Small | 0px | typography/label/letter-spacing |
| status=Warning, size=Small | 0px | typography/label/letter-spacing |
| status=Default, size=Medium | 0px | typography/body/letter-spacing |
| status=Negative, size=Medium | 0px | typography/body/letter-spacing |
| status=Positive, size=Medium | 0px | typography/body/letter-spacing |
| status=Warning, size=Medium | 0px | typography/body/letter-spacing |
text.line-height8
| status=Default, size=Small | 14px | typography/label/line-height |
| status=Negative, size=Small | 14px | typography/label/line-height |
| status=Positive, size=Small | 14px | typography/label/line-height |
| status=Warning, size=Small | 14px | typography/label/line-height |
| status=Default, size=Medium | 22px | typography/body/line-height |
| status=Negative, size=Medium | 22px | typography/body/line-height |
| status=Positive, size=Medium | 22px | typography/body/line-height |
| status=Warning, size=Medium | 22px | typography/body/line-height |