SoneSone
Typography

Text & Span

How rich text composition works in Sone.

Text(...) is a paragraph block. Span(...) is an inline styled segment inside it. A single Text node can contain a mix of plain strings and Spans, each with its own styling.

Text(
  "Revenue grew ",
  Span("+22%").color("green").weight("bold"),
  " year over year.",
).size(16)

This is the core of Sone's rich-text model. There is no separate "rich text" mode — every Text node is rich.

Plain text

When you don't need inline styling, just pass strings:

Text("A simple paragraph of body copy.").size(14).lineHeight(1.6)

Mixed strings and spans

Children of Text can be any mix of strings and Spans:

Text(
  "We just released ",
  Span("v2.0").weight("bold"),
  " — ",
  Span("read the announcement").color("blue").underline(),
  ".",
).size(14)

Span — what it can do

A Span accepts every text-styling method that Text does, plus a few span-specific ones:

Span("highlighted")
  .color("orange")
  .weight("bold")
  .size(14)
  .underline(2)
  .highlight("yellow")
  .strokeColor("black").strokeWidth(0.5)
  .dropShadow("0 1px 2px rgba(0,0,0,0.4)")
  .offsetY(-2)         // baseline offset

TextDefault — inherited styling

TextDefault(...) cascades text styling to all descendant Text and Span nodes. Useful when many nodes share the same font or color:

TextDefault(
  Column(
    Text("Heading").size(20).weight("bold"),
    Text("Body copy that inherits the font and color.").size(12),
  ).gap(8),
)
  .font("GeistMono")
  .color("#111")

Per-node properties always win over inherited defaults.

What's next