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

NameDescriptionPropertyTypeDefaultAllowed values
alignAligns the text within its line.alignkeywordstartstart, center
asThe 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.askeywordpp, span, div, small, strong, label, h1, h2, h3, h4, h5, h6
fontUse sans for UI text rendered outside its original font context; inherit preserves the surrounding typeface.fontkeywordinheritinherit, sans
growLets this text use remaining inline space in a flex row, including when truncated.growbooleanfalse—
sizeThe 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.sizekeywordinheritinherit, xs, sm, md, lg
toneThe 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.tonekeyworddefaultdefault, secondary, muted, accent, accent-secondary, accent-tertiary, danger, success, info, warning
trimRemoves 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.trimbooleanfalse—
truncateKeeps the text on one line, ending in an ellipsis when it does not fit.truncatebooleanfalse—
variantbody 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.variantkeywordbodybody, display, heading, label, caption, eyebrow, code
weightThe weight. inherit keeps the surrounding weight; medium and semibold emphasise a name or a figure.weightkeywordinheritinherit, normal, medium, semibold
wrapUse anywhere for unbroken values such as long destinations; ordinary prose keeps its normal wrapping.wrapkeywordnormalnormal, anywhere

Framework props

NameTypeDefaultAllowed values
align"start" | "center"startstart, center
as"p" | "span" | "div" | "small" | "strong" | "label" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"pp, span, div, small, strong, label, h1, h2, h3, h4, h5, h6
font"inherit" | "sans"inheritinherit, sans
growbooleanfalse—
size"inherit" | "xs" | "sm" | "md" | "lg"inheritinherit, xs, sm, md, lg
tone"default" | "secondary" | "muted" | "accent" | "accent-secondary" | "accent-tertiary" | "danger" | "success" | "info" | "warning"defaultdefault, secondary, muted, accent, accent-secondary, accent-tertiary, danger, success, info, warning
trimbooleanfalse—
truncatebooleanfalse—
variant"body" | "display" | "heading" | "label" | "caption" | "eyebrow" | "code"bodybody, display, heading, label, caption, eyebrow, code
weight"inherit" | "normal" | "medium" | "semibold"inheritinherit, normal, medium, semibold
wrap"normal" | "anywhere"normalnormal, anywhere

Events

No events.

Slots

NamePurpose
defaultDefault child content.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--ui-text-muted——
--ui-text-secondary——

Shared tokens

TokenFallback
--ui-accent-secondary-subtle-text—
--ui-accent-subtle-text—
--ui-accent-tertiary-subtle-text—
--ui-danger-subtle-text—
--ui-font-displayinherit | var(--ui-font-family-sans)
--ui-font-family-mono—
--ui-font-family-sanssans-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-colorvar(--ui-accent-subtle-text)
--ui-success-subtle-text—
--ui-warning-subtle-text—