component
Tag
A compact 20px label with small corners, for what a piece of content is about.
import { Tag } from 'rost/components/tags/Tag'
<Tag>Fintech</Tag>
<Tag href="/work?tag=fintech" aria-label="Projects tagged Fintech">Fintech</Tag>src/components/tags/Tag.tsx
import type { AnchorHTMLAttributes, ReactNode } from 'react'
import './tags.css'
export interface TagProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
/** The word. */
readonly children?: ReactNode
/** Greys it out. A disabled linked tag drops its href. */
readonly disabled?: boolean
}
const b = 'rost-tags'
/**
* What something is about: a discipline, a platform. Static it's a `<span>`.
* Give it an `href` and it becomes a link, with hover, press and focus.
* Built from contracts/tags.contract.yaml.
*/
export function Tag({ children, href, disabled = false, className, ...rest }: TagProps) {
const cls = [b, `${b}--type-primary`, className].filter(Boolean).join(' ')
const inner = (
<>
<span className={`${b}__label`}>{children}</span>
<span className={`${b}__focus-ring`} />
</>
)
if (href === undefined) return <span className={cls} aria-disabled={disabled || undefined} {...(rest as object)}>{inner}</span>
return <a className={cls} href={disabled ? undefined : href} aria-disabled={disabled || undefined} {...rest}>{inner}</a>
}When to use
- Used to say what something is about: a discipline, a platform, a topic.
When not to use
- A status: Badge. A filter: Chip.
Behaviour
One type (Primary) and five states. The hover and press states are there for tags that link to a filtered list. A tag that doesn't go anywhere shouldn't have them.
Accessibility
- Static: a
<span>, or<li>items in a<ul>when there are a few. - Linking: an
<a>whose name gives the context ("Projects tagged Fintech"). - Focus:
focus-ring, only on tags that link.
Content
One word if you can. Capitals only for proper nouns.
Props
| Prop | Type | Values | Default |
|---|---|---|---|
| type | enum | Primary | Primary |
States: Default, Hover, Pressed, Focus, Disabled.
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 | justify-content | center | layout |
| root | align-items | center | layout |
| root | display | inline-flex | layout |
| root | vertical-align | middle | layout |
| root | position | relative | layout |
| root | text-decoration | none | Figma frame size |
| root | gap | 8px | spacing/gap/small |
| root | height | 20px | spacing/size/xsmall |
| root | padding-bottom | 2px | spacing/gap/3xsmall |
| root | padding-top | 0px | spacing/gap/none |
| root | padding-left | 8px | spacing/gap/small |
| root | padding-right | 8px | spacing/gap/small |
| root | border-radius | 4px | spacing/radius/xsmall |
| root | background | #3a3a3a | actions/secondary/regular/default/background |
| root | outline | none | Figma frame size |
| label | flex-shrink | 0 | layout |
| label | white-space | nowrap | layout |
| label | text-align | center | layout |
| label | position | relative | layout |
| label | font-family | 'General Sans Variable' | font-family/base |
| label | font-weight | 500 | typography/label/font-weight/medium |
| label | line-height | 14px | typography/label/line-height |
| label | color | #d3d9d5 | actions/secondary/regular/default/label |
| label | font-size | 12px | typography/label/font-size |
| label | letter-spacing | 0px | typography/label/letter-spacing |
| focus-ring | display | none | layout |
| focus-ring | position | absolute | layout |
| focus-ring | inset | calc(-1 * var(--spacing-gap-2xsmall)) | layout |
| focus-ring | border-style | solid | no token yet |
| focus-ring | border-width | 2px | spacing/outline/medium |
| focus-ring | border-color | #999 | actions/secondary/regular/focus/focus-ring |
| focus-ring | border-radius | 8px | spacing/radius/small |
| focus-ring | pointer-events | none | Figma frame size |
Changes by variant
focus-ring.display1
| type=Primary, state=Focus | block | layout |
label.color4
| type=Primary, state=Hover | #ededed | actions/secondary/regular/hover/label |
| type=Primary, state=Pressed | #d3d9d5 | actions/secondary/regular/pressed/label |
| type=Primary, state=Focus | #d3d9d5 | actions/secondary/regular/focus/label |
| type=Primary, state=Disabled | #d3d9d5 | actions/secondary/regular/disabled/label |
root.background4
| type=Primary, state=Hover | #747573 | actions/secondary/regular/hover/background |
| type=Primary, state=Pressed | #999 | actions/secondary/regular/pressed/background |
| type=Primary, state=Focus | #3a3a3a | actions/secondary/regular/focus/background |
| type=Primary, state=Disabled | #3a3a3a | actions/secondary/regular/disabled/background |
root.opacity1
| type=Primary, state=Disabled | 0.24 | opacity/gentle |