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
linesprop - 🔤 Semantic HTML: Render as different HTML tags (span, p, div, h1-h6, label, code)
- ♿ Accessible:
as="h1"–"h6"setsrole="heading"+aria-levelautomatically - 🎭 Italic Style: Font style support
- 🔧 Customizable: CSS custom properties for full control
Source Code
View Source Code
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
<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.
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.
Label
Medium weight text for form labels and UI labels.
Caption
Smaller, secondary text for additional context or metadata.
Overline
Uppercase text with letter spacing, ideal for categories or eyebrow text.
Code
Monospace text for inline code snippets.
Size Options
Choose from 6 size steps. The token scale resolved depends on the active variant:
size | Body scale token | Value | Heading scale token | Value |
|---|---|---|---|---|
xs | --text-xs | 12px | --heading-xs | 14px |
sm | --text-sm | 14px | --heading-sm | 16px |
md | --text-base | 16px | --heading-md | 24px |
lg | --text-lg | 18px | --heading-lg | 32px |
xl | --text-xl | 20px | --heading-xl | 48px |
2xl | --text-2xl | 24px | --heading-2xl | 64px |
variant="heading" resolves sizes against the heading scale. All other variants (body, label, caption, overline, code) resolve against the body scale.
Colors
Semantic Colors
Use semantic colors to convey meaning and maintain consistency.
Text Colors
Automatic text colors that adapt to your theme. Each value maps to a semantic --text-color-* token from the theme.
color | Token | Contrast step | WCAG |
|---|---|---|---|
heading | --text-color-heading | --color-contrast-900 | AAA |
body | --text-color-body | --color-contrast-800 | AAA |
muted | --text-color-secondary | --color-contrast-600 | AA |
tertiary | --text-color-tertiary | --color-contrast-500 | AA (large text) |
disabled | --text-color-disabled | --color-contrast-400 | decorative only |
contrast | --text-color-contrast | --color-contrast-100 | for dark backgrounds |
Font Weights
Four weight options for emphasis control.
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.
Special Features
Truncate
Enable single-line truncation with ellipsis for overflow text.
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.
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.
Semantic HTML Tags
The as attribute controls document semantics. For h1–h6, 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, h1–h6) render as display: block. Inline elements (span, label, code) render as display: inline.
Combined Examples
Card Header
Combine multiple text components for rich layouts.
Form Field
Label with helper text pattern.
Status Messages
Use semantic colors for feedback.
Truncated Filename
Show file information with truncation using width constraints.
API Reference
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
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) |
truncate | boolean | — | Single-line truncation with ellipsis |
lines | number | — | Clamp to N lines with ellipsis (multi-line truncation) |
italic | boolean | — | Italic font style |
as | 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'code' | — | Semantic element; h1–h6 auto-sets role="heading" + aria-level |
Variant Defaults
| Variant | Default size | Default weight | Default color | Line height | Letter 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
| Slot | Description |
|---|---|
| (default) | Text content |
CSS Custom Properties
| Property | Description | Default |
|---|---|---|
--text-size | Font size | var(--text-base) |
--text-weight | Font weight | var(--font-normal) |
--text-color | Text color | var(--text-color-body) |
--text-line-height | Line height | var(--leading-normal) |
--text-letter-spacing | Letter spacing | normal |
Accessibility
The text component follows WAI-ARIA best practices.
bit-text
✅ Automatic Heading ARIA
as="h1"throughas="h6"automatically setsrole="heading"and the matchingaria-level(1–6) on the host element. Screen readers announce the correct heading level without any extra markup.- Changing
asdynamically (e.g. by removing the attribute) removes both attributes.
✅ Semantic Structure
- Use the
asattribute to give the element correct document semantics (h1–h6,p,label, etc.). as="span",as="label", andas="code"render asdisplay: inlinematching their native HTML counterparts.
✅ Screen Readers
- Uses
remunits 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
asattribute to render a semantically correct element (h1–h6,p,label,code) — especially inside forms, articles, and page headers. - Use
variant="caption"withcolor="muted"for helper text, timestamps, and metadata. - Pair
variant="overline"withsize="xs"for category labels and eyebrow headings. - Use
truncate(single-line) orlines="N"(multi-line) with a width constraint on the element or its container. - Prefer
linesovertruncatefor card bodies and article previews where two or three lines are acceptable. - Set an explicit
sizewhen usingvariant="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
coloralone to convey meaning — always pair with a descriptive text label. - Use
bit-textas a replacement for native block elements (<p>,<h2>, etc.) in long-form content — prefer theasattribute instead. - Set both
truncateandlineson 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.