Skip to content

Text

A versatile typography component with semantic variants for consistent text styling across your application. Provides complete control over typography with design system integration and accessibility built-in.

Features

  • 🎨 6 Semantic Variants: body, heading, label, caption, overline, code
  • 📏 6 Sizes: xs, sm, md, lg, xl, 2xl (body scale) — heading variant uses the heading scale
  • ⚖️ 4 Font Weights: normal, medium, semibold, bold
  • 🌈 12 Colors: semantic (primary, secondary, info, success, warning, error) + text colors (heading, body, muted, tertiary, disabled, contrast)
  • 📐 4 Alignments: left, center, right, justify
  • ✂️ Truncate: Single-line ellipsis truncation
  • 📋 Line Clamp: Multi-line truncation with ellipsis via lines prop
  • 🔤 Semantic HTML: Render as different HTML tags (span, p, div, h1-h6, label, code)
  • Accessible: as="h1"–"h6" sets role="heading" + aria-level automatically
  • 🎭 Italic Style: Font style support
  • 🔧 Customizable: CSS custom properties for full control

Source Code

View Source Code
ts
import { defineComponent, html, typed, watch } from '@vielzeug/craftit';

import styles from './text.css?inline';

/** Text component properties */
export type BitTextProps = {
  /** Text alignment */
  align?: 'left' | 'center' | 'right' | 'justify';
  /** Semantic HTML element to render as — sets the correct ARIA role/level on the host */
  as?: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'code';
  /** Text color (semantic + theme colors) */
  color?:
    | 'primary'
    | 'secondary'
    | 'info'
    | 'success'
    | 'warning'
    | 'error'
    | 'heading'
    | 'body'
    | 'muted'
    | 'tertiary'
    | 'disabled'
    | 'contrast';
  /** Italic text style */
  italic?: boolean;
  /** Clamp text to N lines with an ellipsis (multi-line truncation) */
  lines?: number;
  /** Text size — maps to --text-* tokens for body variants and --heading-* tokens for the heading variant */
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
  /** Enable single-line text truncation with ellipsis */
  truncate?: boolean;
  /** Text semantic variant */
  variant?: 'body' | 'heading' | 'label' | 'caption' | 'overline' | 'code';
  /** Font weight */
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
};

/**
 * A typography component with semantic variants and responsive sizing.
 *
 * @element bit-text
 *
 * @attr {string} variant - Text variant: 'body' | 'heading' | 'label' | 'caption' | 'overline' | 'code'
 * @attr {string} size - Font size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'. Maps to --text-* tokens for body variants and --heading-* tokens for the heading variant.
 * @attr {string} weight - Font weight: 'normal' | 'medium' | 'semibold' | 'bold'
 * @attr {string} color - Text color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | 'heading' | 'body' | 'muted' | 'tertiary' | 'disabled' | 'contrast'
 * @attr {string} align - Text alignment: 'left' | 'center' | 'right' | 'justify'
 * @attr {boolean} truncate - Truncate text with ellipsis when it overflows (single-line)
 * @attr {number} lines - Clamp to N lines with ellipsis (multi-line truncation)
 * @attr {boolean} italic - Italic text style
 * @attr {string} as - Semantic HTML element: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'code'
 *
 * @slot - Text content
 *
 * @cssprop --text-size - Font size
 * @cssprop --text-weight - Font weight
 * @cssprop --text-color - Text color
 * @cssprop --text-line-height - Line height
 * @cssprop --text-letter-spacing - Letter spacing
 *
 * @example
 * ```html
 * <bit-text variant="heading" size="3xl" weight="bold">Welcome</bit-text>
 * <bit-text as="h2" variant="heading" size="xl">Section title</bit-text>
 * <bit-text color="primary" weight="semibold">Important notice</bit-text>
 * <bit-text variant="code">npm install</bit-text>
 * <bit-text lines="2">Long paragraph that clamps at two lines…</bit-text>
 * ```
 */

export const TEXT_TAG = defineComponent<BitTextProps>({
  props: {
    align: typed<BitTextProps['align']>(undefined),
    as: typed<BitTextProps['as']>(undefined),
    color: typed<BitTextProps['color']>(undefined),
    italic: typed<boolean>(false),
    lines: typed<number | undefined>(undefined, { type: Number }),
    size: typed<BitTextProps['size']>(undefined),
    truncate: typed<boolean>(false),
    variant: typed<BitTextProps['variant']>(undefined),
    weight: typed<BitTextProps['weight']>(undefined),
  },
  setup({ host, props }) {
    // Single watcher drives both role + aria-level from `as`.
    // h1–h6 → role="heading" + aria-level; everything else → remove both.
    watch(
      props.as,
      (tag) => {
        const match = /^h([1-6])$/.exec((tag as string | undefined) ?? '');

        if (match) {
          host.setAttribute('role', 'heading');
          host.setAttribute('aria-level', match[1]);
        } else {
          host.removeAttribute('role');
          host.removeAttribute('aria-level');
        }
      },
      { immediate: true },
    );
    // Drive --_lines on the host so the CSS line-clamp rule works.
    watch(
      props.lines,
      (n) => {
        if (n != null) host.style.setProperty('--_lines', String(n));
        else host.style.removeProperty('--_lines');
      },
      { immediate: true },
    );

    return html`<slot></slot>`;
  },
  styles: [styles],
  tag: 'bit-text',
});

Basic Usage

html
<bit-text>Regular paragraph text</bit-text>

<script type="module">
  import '@vielzeug/buildit/text';
</script>

Variants

Body (Default)

Normal paragraph text with standard line height. The default variant for general content.

PreviewCode
RTL

Heading

Emphasized text for headings with tighter line height and semibold weight. Defaults to md on the heading scale (--heading-md, 1.5rem / 24px) — set size explicitly to pick a different step.

Heading scale vs body scale

variant="heading" maps the size attribute to the heading scale (--heading-xs--heading-2xl) rather than the body text scale. This gives a much wider range: from 0.875rem (xs) all the way to 4rem (2xl). See Size Options for the full mapping.

PreviewCode
RTL

Label

Medium weight text for form labels and UI labels.

PreviewCode
RTL

Caption

Smaller, secondary text for additional context or metadata.

PreviewCode
RTL

Overline

Uppercase text with letter spacing, ideal for categories or eyebrow text.

PreviewCode
RTL

Code

Monospace text for inline code snippets.

PreviewCode
RTL

Size Options

Choose from 6 size steps. The token scale resolved depends on the active variant:

sizeBody scale tokenValueHeading scale tokenValue
xs--text-xs12px--heading-xs14px
sm--text-sm14px--heading-sm16px
md--text-base16px--heading-md24px
lg--text-lg18px--heading-lg32px
xl--text-xl20px--heading-xl48px
2xl--text-2xl24px--heading-2xl64px

variant="heading" resolves sizes against the heading scale. All other variants (body, label, caption, overline, code) resolve against the body scale.

PreviewCode
RTL

Colors

Semantic Colors

Use semantic colors to convey meaning and maintain consistency.

PreviewCode
RTL

Text Colors

Automatic text colors that adapt to your theme. Each value maps to a semantic --text-color-* token from the theme.

colorTokenContrast stepWCAG
heading--text-color-heading--color-contrast-900AAA
body--text-color-body--color-contrast-800AAA
muted--text-color-secondary--color-contrast-600AA
tertiary--text-color-tertiary--color-contrast-500AA (large text)
disabled--text-color-disabled--color-contrast-400decorative only
contrast--text-color-contrast--color-contrast-100for dark backgrounds
PreviewCode
RTL

Font Weights

Four weight options for emphasis control.

PreviewCode
RTL

Text Alignment

Control text alignment for layout purposes.

Display behaviour

bit-text is display: block by default. as="span", as="label", and as="code" switch it to display: inline. The align attribute has no effect on these inline variants unless display: block is also applied.

PreviewCode
RTL

Special Features

Truncate

Enable single-line truncation with ellipsis for overflow text.

PreviewCode
RTL

Usage

The truncate attribute is a boolean flag. Set a width constraint on the element or its container for truncation to apply.

Line Clamp

Clamp text to a fixed number of lines with an ellipsis — ideal for card descriptions and teasers.

PreviewCode
RTL

Usage

Use lines for multi-line truncation and truncate for single-line. They are mutually exclusive — lines takes precedence if both are present.

Italic Style

Apply italic styling to text.

PreviewCode
RTL

Semantic HTML Tags

The as attribute controls document semantics. For h1h6, the component automatically sets role="heading" and the correct aria-level on the host so screen readers announce the correct heading level — no extra ARIA needed.

Block elements (p, div, h1h6) render as display: block. Inline elements (span, label, code) render as display: inline.

PreviewCode
RTL

Combined Examples

Card Header

Combine multiple text components for rich layouts.

PreviewCode
RTL

Form Field

Label with helper text pattern.

PreviewCode
RTL

Status Messages

Use semantic colors for feedback.

PreviewCode
RTL

Truncated Filename

Show file information with truncation using width constraints.

PreviewCode
RTL

API Reference

Attributes

AttributeTypeDefaultDescription
variant'body' | 'heading' | 'label' | 'caption' | 'overline' | 'code'Text variant style
size'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'Font size. variant="heading" resolves against --heading-* tokens; all other variants resolve against --text-* tokens
weight'normal' | 'medium' | 'semibold' | 'bold'Font weight (falls back to variant default, then normal)
color'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | 'heading' | 'body' | 'muted' | 'tertiary' | 'disabled' | 'contrast'Text color (falls back to variant default, then inherit)
align'left' | 'center' | 'right' | 'justify'Text alignment (forces display: block)
truncatebooleanSingle-line truncation with ellipsis
linesnumberClamp to N lines with ellipsis (multi-line truncation)
italicbooleanItalic font style
as'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'code'Semantic element; h1h6 auto-sets role="heading" + aria-level

Variant Defaults

VariantDefault sizeDefault weightDefault colorLine heightLetter spacing
body--text-base (16px)--font-normal (400)--text-color-body (contrast-800)--leading-normal (1.5)normal
heading--heading-md (24px)--font-semibold (600)--text-color-heading (contrast-900)--leading-tight (1.15)--tracking-header (-0.025em)
label--text-sm (14px)--font-medium (500)--text-color-heading (contrast-900)--leading-snug (1.375)normal
caption--text-sm (14px)--font-normal (400)--text-color-secondary (contrast-600)--leading-normal (1.5)normal
overline--text-xs (12px)--font-semibold (600)--text-color-body (contrast-800)--leading-none (1)0.08em
code--text-sm (14px)--font-normal (400)--text-color-body (contrast-800)--leading-normal (1.5)normal

Slots

SlotDescription
(default)Text content

CSS Custom Properties

PropertyDescriptionDefault
--text-sizeFont sizevar(--text-base)
--text-weightFont weightvar(--font-normal)
--text-colorText colorvar(--text-color-body)
--text-line-heightLine heightvar(--leading-normal)
--text-letter-spacingLetter spacingnormal

Accessibility

The text component follows WAI-ARIA best practices.

bit-text

Automatic Heading ARIA

  • as="h1" through as="h6" automatically sets role="heading" and the matching aria-level (1–6) on the host element. Screen readers announce the correct heading level without any extra markup.
  • Changing as dynamically (e.g. by removing the attribute) removes both attributes.

Semantic Structure

  • Use the as attribute to give the element correct document semantics (h1h6, p, label, etc.).
  • as="span", as="label", and as="code" render as display: inline matching their native HTML counterparts.

Screen Readers

  • Uses rem units to respect user's browser font size preferences.
  • Maintains WCAG-compliant line height (1.5 default for body text).
  • Text colors maintain accessible contrast ratios with backgrounds.

Best Practice

Always set as for headings and form labels. For page titles, pair as="h1" with variant="heading" and the appropriate size to get correct visual hierarchy and document semantics in one attribute set.

Best Practices

Do:

  • Use the as attribute to render a semantically correct element (h1h6, p, label, code) — especially inside forms, articles, and page headers.
  • Use variant="caption" with color="muted" for helper text, timestamps, and metadata.
  • Pair variant="overline" with size="xs" for category labels and eyebrow headings.
  • Use truncate (single-line) or lines="N" (multi-line) with a width constraint on the element or its container.
  • Prefer lines over truncate for card bodies and article previews where two or three lines are acceptable.
  • Set an explicit size when using variant="heading" to select the correct step from the heading scale.

Don't:

  • Forget that variant="heading" uses the heading scale — size="2xl" on a heading renders at 4rem (64px), not 1.5rem.
  • Rely on color alone to convey meaning — always pair with a descriptive text label.
  • Use bit-text as a replacement for native block elements (<p>, <h2>, etc.) in long-form content — prefer the as attribute instead.
  • Set both truncate and lines on the same element; use one or the other.
  • Use color="muted" for headings — it resolves to --text-color-secondary (contrast-600) which may not meet AAA for small heading sizes.