Text
Text roles for body copy, display titles, headings, labels, captions, eyebrows and code. Choose the document element independently from its visual role; shared typography and semantic tones keep text consistent.
Text hierarchy
Heading, body and supporting text at normal reading size.
Clear ideas.
Space to make them happen.
Updated today
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<ui-stack
gap="xs"
>
<ui-text
variant="heading"
as="h4"
>Clear ideas.</ui-text>
<ui-text>Space to make them happen.</ui-text>
<ui-text
variant="caption"
>Updated today</ui-text>
</ui-stack><script setup>
import { UiStack, UiText } from "@threadlabs/looma";
</script>
<template>
<UiStack
gap="xs"
>
<UiText
variant="heading"
as="h4"
>Clear ideas.</UiText>
<UiText>Space to make them happen.</UiText>
<UiText
variant="caption"
>Updated today</UiText>
</UiStack>
</template>import { UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="xs"
>
<UiText
variant="heading"
as="h4"
>Clear ideas.</UiText>
<UiText>Space to make them happen.</UiText>
<UiText
variant="caption"
>Updated today</UiText>
</UiStack>;
}/** @jsxImportSource preact */
import { UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="xs"
>
<UiText
variant="heading"
as="h4"
>Clear ideas.</UiText>
<UiText>Space to make them happen.</UiText>
<UiText
variant="caption"
>Updated today</UiText>
</UiStack>;
}<script>
import { UiStack, UiText } from "@threadlabs/looma";
</script>
<UiStack
gap="xs"
>
<UiText
variant="heading"
as="h4"
>Clear ideas.</UiText>
<UiText>Space to make them happen.</UiText>
<UiText
variant="caption"
>Updated today</UiText>
</UiStack>import { Component } from "@angular/core";
import { UiStack, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiText],
template: `
<div
ui-stack
[gap]="'xs'"
>
<ng-template
ui-text
[variant]="'heading'"
[as]="'h4'"
>Clear ideas.</ng-template>
<ng-template
ui-text
>Space to make them happen.</ng-template>
<ng-template
ui-text
[variant]="'caption'"
>Updated today</ng-template>
</div>
`,
})
export default class Example {}Tones
A tone says what the text is. Default for content, secondary for supporting prose, muted for metadata and hints, info for something moving forward, warning for something that needs attention, danger for an error.
Default: the content itself.
Secondary: a description that supports it.
Muted, small: Edited 2h ago
Info: Scheduled for tomorrow.
Warning: Add a billing address before you publish.
Danger: That address is taken.
A supporting brand highlight.
Another category in the same palette.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<ui-stack
gap="xs"
>
<ui-text>Default: the content itself.</ui-text>
<ui-text
tone="secondary"
>Secondary: a description that supports it.</ui-text>
<ui-text
tone="muted"
size="sm"
>Muted, small: Edited 2h ago</ui-text>
<ui-text
tone="info"
size="sm"
>Info: Scheduled for tomorrow.</ui-text>
<ui-text
tone="warning"
size="sm"
>Warning: Add a billing address before you publish.</ui-text>
<ui-text
tone="danger"
size="sm"
>Danger: That address is taken.</ui-text>
<ui-text
tone="accent-secondary"
>A supporting brand highlight.</ui-text>
<ui-text
tone="accent-tertiary"
>Another category in the same palette.</ui-text>
</ui-stack><script setup>
import { UiStack, UiText } from "@threadlabs/looma";
</script>
<template>
<UiStack
gap="xs"
>
<UiText>Default: the content itself.</UiText>
<UiText
tone="secondary"
>Secondary: a description that supports it.</UiText>
<UiText
tone="muted"
size="sm"
>Muted, small: Edited 2h ago</UiText>
<UiText
tone="info"
size="sm"
>Info: Scheduled for tomorrow.</UiText>
<UiText
tone="warning"
size="sm"
>Warning: Add a billing address before you publish.</UiText>
<UiText
tone="danger"
size="sm"
>Danger: That address is taken.</UiText>
<UiText
tone="accent-secondary"
>A supporting brand highlight.</UiText>
<UiText
tone="accent-tertiary"
>Another category in the same palette.</UiText>
</UiStack>
</template>import { UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="xs"
>
<UiText>Default: the content itself.</UiText>
<UiText
tone="secondary"
>Secondary: a description that supports it.</UiText>
<UiText
tone="muted"
size="sm"
>Muted, small: Edited 2h ago</UiText>
<UiText
tone="info"
size="sm"
>Info: Scheduled for tomorrow.</UiText>
<UiText
tone="warning"
size="sm"
>Warning: Add a billing address before you publish.</UiText>
<UiText
tone="danger"
size="sm"
>Danger: That address is taken.</UiText>
<UiText
tone="accent-secondary"
>A supporting brand highlight.</UiText>
<UiText
tone="accent-tertiary"
>Another category in the same palette.</UiText>
</UiStack>;
}/** @jsxImportSource preact */
import { UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="xs"
>
<UiText>Default: the content itself.</UiText>
<UiText
tone="secondary"
>Secondary: a description that supports it.</UiText>
<UiText
tone="muted"
size="sm"
>Muted, small: Edited 2h ago</UiText>
<UiText
tone="info"
size="sm"
>Info: Scheduled for tomorrow.</UiText>
<UiText
tone="warning"
size="sm"
>Warning: Add a billing address before you publish.</UiText>
<UiText
tone="danger"
size="sm"
>Danger: That address is taken.</UiText>
<UiText
tone="accent-secondary"
>A supporting brand highlight.</UiText>
<UiText
tone="accent-tertiary"
>Another category in the same palette.</UiText>
</UiStack>;
}<script>
import { UiStack, UiText } from "@threadlabs/looma";
</script>
<UiStack
gap="xs"
>
<UiText>Default: the content itself.</UiText>
<UiText
tone="secondary"
>Secondary: a description that supports it.</UiText>
<UiText
tone="muted"
size="sm"
>Muted, small: Edited 2h ago</UiText>
<UiText
tone="info"
size="sm"
>Info: Scheduled for tomorrow.</UiText>
<UiText
tone="warning"
size="sm"
>Warning: Add a billing address before you publish.</UiText>
<UiText
tone="danger"
size="sm"
>Danger: That address is taken.</UiText>
<UiText
tone="accent-secondary"
>A supporting brand highlight.</UiText>
<UiText
tone="accent-tertiary"
>Another category in the same palette.</UiText>
</UiStack>import { Component } from "@angular/core";
import { UiStack, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiText],
template: `
<div
ui-stack
[gap]="'xs'"
>
<ng-template
ui-text
>Default: the content itself.</ng-template>
<ng-template
ui-text
[tone]="'secondary'"
>Secondary: a description that supports it.</ng-template>
<ng-template
ui-text
[tone]="'muted'"
[size]="'sm'"
>Muted, small: Edited 2h ago</ng-template>
<ng-template
ui-text
[tone]="'info'"
[size]="'sm'"
>Info: Scheduled for tomorrow.</ng-template>
<ng-template
ui-text
[tone]="'warning'"
[size]="'sm'"
>Warning: Add a billing address before you publish.</ng-template>
<ng-template
ui-text
[tone]="'danger'"
[size]="'sm'"
>Danger: That address is taken.</ng-template>
<ng-template
ui-text
[tone]="'accent-secondary'"
>A supporting brand highlight.</ng-template>
<ng-template
ui-text
[tone]="'accent-tertiary'"
>Another category in the same palette.</ng-template>
</div>
`,
})
export default class Example {}Typography roles
One shared type family gives the Fellowship office titles, section headings, labels, captions and eyebrows. Choose document heading levels separately from visual roles.
Fellowship office
There and back by Friday
This week's expedition
Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.
Next stand-up
Tuesday, after second breakfast
fellowship.example.com
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<ui-stack
gap="s"
>
<ui-text
variant="eyebrow"
tone="secondary"
>Fellowship office</ui-text>
<ui-text
variant="display"
as="h1"
>There and back by Friday</ui-text>
<ui-text
variant="heading"
as="h2"
>This week's expedition</ui-text>
<ui-text>Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.</ui-text>
<ui-text
variant="label"
>Next stand-up</ui-text>
<ui-text
variant="caption"
tone="secondary"
>Tuesday, after second breakfast</ui-text>
<ui-text
variant="code"
tone="secondary"
size="sm"
>fellowship.example.com</ui-text>
</ui-stack><script setup>
import { UiStack, UiText } from "@threadlabs/looma";
</script>
<template>
<UiStack
gap="s"
>
<UiText
variant="eyebrow"
tone="secondary"
>Fellowship office</UiText>
<UiText
variant="display"
as="h1"
>There and back by Friday</UiText>
<UiText
variant="heading"
as="h2"
>This week's expedition</UiText>
<UiText>Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.</UiText>
<UiText
variant="label"
>Next stand-up</UiText>
<UiText
variant="caption"
tone="secondary"
>Tuesday, after second breakfast</UiText>
<UiText
variant="code"
tone="secondary"
size="sm"
>fellowship.example.com</UiText>
</UiStack>
</template>import { UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="s"
>
<UiText
variant="eyebrow"
tone="secondary"
>Fellowship office</UiText>
<UiText
variant="display"
as="h1"
>There and back by Friday</UiText>
<UiText
variant="heading"
as="h2"
>This week's expedition</UiText>
<UiText>Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.</UiText>
<UiText
variant="label"
>Next stand-up</UiText>
<UiText
variant="caption"
tone="secondary"
>Tuesday, after second breakfast</UiText>
<UiText
variant="code"
tone="secondary"
size="sm"
>fellowship.example.com</UiText>
</UiStack>;
}/** @jsxImportSource preact */
import { UiStack, UiText } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="s"
>
<UiText
variant="eyebrow"
tone="secondary"
>Fellowship office</UiText>
<UiText
variant="display"
as="h1"
>There and back by Friday</UiText>
<UiText
variant="heading"
as="h2"
>This week's expedition</UiText>
<UiText>Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.</UiText>
<UiText
variant="label"
>Next stand-up</UiText>
<UiText
variant="caption"
tone="secondary"
>Tuesday, after second breakfast</UiText>
<UiText
variant="code"
tone="secondary"
size="sm"
>fellowship.example.com</UiText>
</UiStack>;
}<script>
import { UiStack, UiText } from "@threadlabs/looma";
</script>
<UiStack
gap="s"
>
<UiText
variant="eyebrow"
tone="secondary"
>Fellowship office</UiText>
<UiText
variant="display"
as="h1"
>There and back by Friday</UiText>
<UiText
variant="heading"
as="h2"
>This week's expedition</UiText>
<UiText>Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.</UiText>
<UiText
variant="label"
>Next stand-up</UiText>
<UiText
variant="caption"
tone="secondary"
>Tuesday, after second breakfast</UiText>
<UiText
variant="code"
tone="secondary"
size="sm"
>fellowship.example.com</UiText>
</UiStack>import { Component } from "@angular/core";
import { UiStack, UiText } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiText],
template: `
<div
ui-stack
[gap]="'s'"
>
<ng-template
ui-text
[variant]="'eyebrow'"
[tone]="'secondary'"
>Fellowship office</ng-template>
<ng-template
ui-text
[variant]="'display'"
[as]="'h1'"
>There and back by Friday</ng-template>
<ng-template
ui-text
[variant]="'heading'"
[as]="'h2'"
>This week's expedition</ng-template>
<ng-template
ui-text
>Frodo and Sam are reviewing the route. Sméagol has booked the quiet meeting room.</ng-template>
<ng-template
ui-text
[variant]="'label'"
>Next stand-up</ng-template>
<ng-template
ui-text
[variant]="'caption'"
[tone]="'secondary'"
>Tuesday, after second breakfast</ng-template>
<ng-template
ui-text
[variant]="'code'"
[tone]="'secondary'"
[size]="'sm'"
>fellowship.example.com</ng-template>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: p
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
align | Aligns the text within its line. | align | keyword | start | start, center |
as | The native element the text renders: a paragraph by default, inline span, small, or strong within a line, a div for a block, a label for a control, or a heading, h1 to h6. Choose its document level independently from its visual variant. The default body variant keeps the existing size and weight behavior. | as | keyword | p | p, span, div, small, strong, label, h1, h2, h3, h4, h5, h6 |
font | Use sans for UI text rendered outside its original font context; inherit preserves the surrounding typeface. | font | keyword | inherit | inherit, sans |
grow | Lets this text use remaining inline space in a flex row, including when truncated. | grow | boolean | false | — |
size | The step on the type scale. inherit (the default) keeps the size of the text around it; xs is for fine print, sm for supporting text such as help and metadata, md is body text, and lg is a lead paragraph. | size | keyword | inherit | inherit, xs, sm, md, lg |
tone | The text's colour, by meaning. default inherits its containing surface or control's foreground. secondary is supporting prose such as a description; muted is quieter still, for metadata and hints; accent draws the eye; danger states an error; success confirms one; info marks something moving forward normally; warning marks something that needs attention. Colour alone does not carry the meaning, so the words must say it too. Supporting brand families use accent-secondary and accent-tertiary; their usable colors derive from the theme, and unset families follow the primary accent. | tone | keyword | default | default, secondary, muted, accent, accent-secondary, accent-tertiary, danger, success, info, warning |
trim | Removes extra font space above the capital height and below the baseline, for compact headings and labels. Prose keeps its existing line-edge space by default. Browsers without text-box-trim retain normal font metrics. | trim | boolean | false | — |
truncate | Keeps the text on one line, ending in an ellipsis when it does not fit. | truncate | boolean | false | — |
variant | body is running text. display is a page title, heading names a section, label identifies a control, and caption is supporting text. These roles set their shared type scale, weight and leading; pair heading or display with an appropriate as heading element. eyebrow is a short, uppercase, tracked label that names what follows, such as a group of links; code sets a value to copy or read exactly, such as an address, in the monospace face. | variant | keyword | body | body, display, heading, label, caption, eyebrow, code |
weight | The weight. inherit keeps the surrounding weight; medium and semibold emphasise a name or a figure. | weight | keyword | inherit | inherit, normal, medium, semibold |
wrap | Use anywhere for unbroken values such as long destinations; ordinary prose keeps its normal wrapping. | wrap | keyword | normal | normal, anywhere |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
align | "start" | "center" | start | start, center |
as | "p" | "span" | "div" | "small" | "strong" | "label" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | p | p, span, div, small, strong, label, h1, h2, h3, h4, h5, h6 |
font | "inherit" | "sans" | inherit | inherit, sans |
grow | boolean | false | — |
size | "inherit" | "xs" | "sm" | "md" | "lg" | inherit | inherit, xs, sm, md, lg |
tone | "default" | "secondary" | "muted" | "accent" | "accent-secondary" | "accent-tertiary" | "danger" | "success" | "info" | "warning" | default | default, secondary, muted, accent, accent-secondary, accent-tertiary, danger, success, info, warning |
trim | boolean | false | — |
truncate | boolean | false | — |
variant | "body" | "display" | "heading" | "label" | "caption" | "eyebrow" | "code" | body | body, display, heading, label, caption, eyebrow, code |
weight | "inherit" | "normal" | "medium" | "semibold" | inherit | inherit, normal, medium, semibold |
wrap | "normal" | "anywhere" | normal | normal, anywhere |
Events
No events.
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-text-muted | — | — |
--ui-text-secondary | — | — |
Shared tokens
| Token | Fallback |
|---|---|
--ui-accent-secondary-subtle-text | — |
--ui-accent-subtle-text | — |
--ui-accent-tertiary-subtle-text | — |
--ui-danger-subtle-text | — |
--ui-font-display | inherit | var(--ui-font-family-sans) |
--ui-font-family-mono | — |
--ui-font-family-sans | sans-serif |
--ui-font-medium | — |
--ui-font-normal | — |
--ui-font-semibold | — |
--ui-font-size-2xl | — |
--ui-font-size-lg | — |
--ui-font-size-md | — |
--ui-font-size-sm | — |
--ui-font-size-xl | — |
--ui-font-size-xs | — |
--ui-info-subtle-text | — |
--ui-line-height | — |
--ui-line-height-relaxed | — |
--ui-line-height-tight | — |
--ui-link-color | var(--ui-accent-subtle-text) |
--ui-success-subtle-text | — |
--ui-warning-subtle-text | — |