Back to Onyx

Text

web/lib/opal/src/components/text/README.md

4.7.0-cloud.68.9 KB
Original Source

Text

Import: import { Text, type TextProps, type TextFont, type TextColor } from "@opal/components";

A styled text component with string-enum props for font preset and color selection. Supports inline markdown rendering via RichStr — pass markdown("*bold* text") as children to enable.

Props

PropTypeDefaultDescription
fontTextFont"main-ui-body"Font preset (size, weight, line-height)
colorTextColor"text-04"Text color
as"p" | "span" | "li" | "h1" | "h2" | "h3""span"HTML tag to render
nowrapbooleanfalsePrevent text wrapping
maxLinesnumberTruncate to N lines with an ellipsis (1 = single-line truncate; 2+ = -webkit-line-clamp)
childrenstring | RichStr | RichNodesPlain string, markdown() for inline markdown, or richNodes() for inline React nodes

No className or style. Text strips both (its props extend WithoutStyles); every aspect of its appearance is driven by font, color, nowrap, and maxLines. For layout concerns (margin, flex, width, alignment) wrap Text in a container or move the utility class to the parent — don't reach for className. All other HTML attributes (id, onClick, title, aria-*, data-*) pass straight through to the rendered element.

TextFont

ValueSizeWeightLine-height
"heading-h1"48px60064px
"heading-h2"24px60036px
"heading-h3"18px60028px
"heading-h3-muted"18px50028px
"main-content-body"16px45024px
"main-content-muted"16px40024px
"main-content-emphasis"16px70024px
"main-content-mono"16px40023px
"main-ui-body"14px50020px
"main-ui-muted"14px40020px
"main-ui-action"14px60020px
"main-ui-mono"14px40020px
"secondary-body"12px40018px
"secondary-action"12px60018px
"secondary-mono"12px40018px
"figure-small-label"10px60014px
"figure-small-value"10px40014px
"figure-keystroke"11px40016px

TextColor

Default: "text-04". Within each numbered scale, 05 is the strongest/most prominent and lower numbers are progressively fainter.

GroupValuesWhen to use
Primarytext-01, text-02, text-03, text-04, text-05Default text on standard light surfaces — 05 for emphasis, 01 for the faintest metadata
Invertedtext-inverted-01text-inverted-05Text on dark or colored surfaces; flips with theme (light in light mode, dark in dark mode)
Fixed light / darktext-light-03, text-light-05, text-dark-03, text-dark-05A fixed light or dark text color that does not flip with the theme
Status — errorstatus-error-01, status-error-02, status-error-05Error / destructive messaging (e.g. validation errors)
Status — successstatus-success-01, status-success-02, status-success-05Success / confirmation messaging
SpecialinheritInherit the surrounding text color (no color class applied) — useful when a parent sets the color

Usage Examples

tsx
import { Text } from "@opal/components";

// Basic
<Text font="main-ui-body" color="text-03">
  Hello world
</Text>

// Heading
<Text font="heading-h2" color="text-05" as="h2">
  Page Title
</Text>

// Inverted (for dark backgrounds)
<Text font="main-ui-body" color="text-inverted-05">
  Light text on dark
</Text>

// As paragraph
<Text font="main-content-body" color="text-03" as="p">
  A full paragraph of text.
</Text>

Inline Markdown via RichStr

Inline markdown is opt-in via the markdown() function, which returns a RichStr. When Text receives a RichStr as children, it parses the inner string as inline markdown. Plain strings are rendered as-is — no parsing, no surprises. Text does not accept arbitrary JSX as children; every non-string child must be branded via markdown() or richNodes().

tsx
import { Text } from "@opal/components";
import { markdown } from "@opal/utils";

// Inline markdown — bold, italic, links, code, strikethrough
<Text font="main-ui-body" color="text-05">
  {markdown("*Hello*, **world**! Visit [Onyx](https://onyx.app) and run `onyx start`.")}
</Text>

// Plain string — no markdown parsing
<Text font="main-ui-body" color="text-03">
  This *stays* as-is, no formatting applied.
</Text>

Supported syntax: **bold**, *italic*, `code`, [link](url), ~~strikethrough~~, \n (newline → ).

Markdown rendering uses react-markdown internally, restricted to inline elements only. http(s) links open in a new tab; mailto: and tel: links open natively. Inline code inherits the parent font size and switches to the monospace family.

Newlines (\n) are converted to elements that inherit the parent's line-height, so line spacing is proportional to the font size. For full block-level markdown (code blocks, headings, lists), use MinimalMarkdown instead.

Using RichStr in component props

Components that want to support optional markdown in their text props should accept string | RichStr:

tsx
import type { RichStr } from "@opal/types";

interface MyComponentProps {
  title: string | RichStr;
  description?: string | RichStr;
}

This avoids API coloring — no markdown boolean needs to be threaded through intermediate components. The decision to use markdown lives at the call site.

Inline React nodes via RichNodes

Some sentences must embed an inline component — most often i18n rich text, where a translated sentence wraps part of itself in a link or button (next-intl t.rich). Markdown cannot express an element with an event handler, so for this case richNodes() brands a ReactNode as deliberate Text children:

tsx
import { Text } from "@opal/components";
import { richNodes } from "@opal/utils";

<Text font="main-ui-body" color="text-04">
  {richNodes(
    t.rich("waitingOnVerification.helpPrompt.text", {
      link: (chunks) => (
        <RequestNewVerificationEmail email={email}>{chunks}</RequestNewVerificationEmail>
      ),
    })
  )}
</Text>

The nodes render verbatim and inherit the font and color presets. The brand keeps the same discipline as RichStr: naked JSX children stay a type error, and the opt-in is visible and greppable at the call site.

Rules of thumb:

  • Prefer a plain string; use markdown() when the formatting is static (bold, code, plain links); use richNodes() only when a real component must sit mid-sentence.
  • Keep the content inline (spans, links, buttons) — never layout JSX.
  • RichNodes is accepted only by Text children. Props typed string | RichStr (title, description, tooltip, …) must stay plain-string-derivable for truncation and aria labels, so do not widen them.

Compatibility

@/refresh-components/texts/Text is an independent legacy component that implements the same font/color presets via a boolean-flag API. It is not a wrapper around this component. New code should import directly from @opal/components.