Guide and migration tool for replacing legacy typography utility classes (d-headline--*, d-body--*, d-label--*, d-code--md, d-fw-*, d-fc-*, d-lh-*, d-truncate, d-ta-*) with the DtText component. Covers automatic migration, edge cases, and class-to-prop mapping.
d-headline--*, d-body--*, d-label--*, d-code--md, d-helper--* on <p>, <span>, <div>, <h1>–<h6>, and <label> with <dt-text>.kind="…" size="…". For new manual code, prefer variant="…" for complete text compositions.d-fs-200 can now be replaced with variant="…" size="200" when you need a composed style plus an exact font-size override.d-fw-*, d-fc-*, d-lh-*, d-truncate, and d-ta-* map to strength, tone, density, truncate, and align props.npx dialtone-migrate-typography.DtText is Dialtone's semantic text component. It exposes typography intent through props (variant, size, density, strength, tone, truncate, align) rather than utility classes, making typography decisions explicit and easier to validate at build time.
For backward compatibility, kind remains supported and keeps the historical composed typography behavior when variant is not set. That means existing <dt-text kind="body" size="300"> output from the migration tool is still valid. Prefer variant for new manual code and for cleanup you do after running the tool.
A migration tool is available to replace typography utility classes with <dt-text> automatically, or you may do so manually.
The migration tool automates the conversion process with several key benefits:
:class bindings, unsupported tag types, font-size utilities)dt-text-migrate review markers flag cases that need a second look, and --dry-run previews which files would change before you write anythingThe tool is ideal for projects with many text elements using utility classes. For small, one-off changes, manual migration may be faster.
Before:
<p class="d-headline--md">Welcome back</p>
<span class="d-body--sm d-fc-secondary d-truncate">
you@example.com
</span>
<label class="d-label--md d-fw-bold">
Display name
</label>
After:
<!-- Current migration tool output -->
<dt-text as="p" kind="headline" size="300">Welcome back</dt-text>
<dt-text kind="body" size="100" tone="secondary" truncate>
you@example.com
</dt-text>
<dt-text as="label" kind="label" size="300" strength="bold">
Display name
</dt-text>
<!-- Preferred manual form for new code -->
<dt-text as="p" variant="headline-md">Welcome back</dt-text>
<dt-text variant="body-xs" tone="secondary" truncate>
you@example.com
</dt-text>
<dt-text as="label" variant="label-md" strength="bold">
Display name
</dt-text>
Before:
<h2 class="d-headline--lg-compact">
Recent calls
</h2>
<div class="d-body--md-compact d-ta-right d-fc-muted">
Last updated just now
</div>
After:
<!-- Current migration tool output -->
<dt-text as="h2" kind="headline" size="500" density="200">
Recent calls
</dt-text>
<dt-text as="div" kind="body" size="300" density="300" align="end" tone="muted">
Last updated just now
</dt-text>
<!-- Preferred manual form for new code -->
<dt-text as="h2" variant="headline-xl" density="200">
Recent calls
</dt-text>
<dt-text as="div" variant="body-md" density="300" align="end" tone="muted">
Last updated just now
</dt-text>
dialtone-migrate-typography scans .vue and .html files for legacy typography utility classes and converts them to <dt-text>. It is included with @dialpad/dialtone-css.
Composed typography classes on <p>, <span>, <div>, <h1>–<h6>, and <label> elements:
d-headline--* (and legacy aliases like d-headline-medium) → kind="headline" + sized-body--* / d-body-* → kind="body" + sized-label--* / d-label-* → kind="label" + sized-code--md / d-code-base → kind="code" size="200"d-helper--* → approximated as kind="body" + density (flagged for review — see below)Override utility classes on any element being migrated, or on existing <dt-text> elements carrying residual utilities:
d-fw-bold/semibold/medium/normal → strength="…"d-lh-100 – d-lh-600 → density="…"d-fc-* (15 tone values) → tone="…"d-truncate → truncate (boolean prop)d-ta-left/right/center/justify → align="start/end/center/justify"d-ff-mono → kind="code"Non-rewriteable tags (<a>, <button>, <li>, <td>, custom Vue components, etc.) are never converted — they carry semantic behavior beyond text and have no clean DtText receiver. If such a tag carries only override utilities (d-fw-*, d-fc-*, etc.) it is left untouched silently. If it carries a composed class, the tool flags it with a review composed class on wrapper tag marker instead of rewriting it (see the markers table below).
Layout containers are skipped, not rewritten. A rewriteable tag carrying a composed class is only converted when it's a genuine text leaf. If it has a layout display class or block/component children (i.e. it's a wrapper, not text), the tool leaves it in place and flags it with a review composed class on wrapper marker.
Patterns the tool can't safely auto-migrate are surfaced with an inline <!-- dt-text-migrate: … --> comment. In most cases the element is left unchanged so the legacy class still appears in the diff for you to handle; the d-helper--* case is the exception (it is rewritten to an approximation and flagged). Run --remove-markers once you've resolved every flagged case to strip all dt-text-migrate comments.
| Marker comment | What triggered it | What to do |
|---|---|---|
<!-- dt-text-migrate: review --> | d-headline--eyebrow / d-headline-eyebrow (uses text-transform: uppercase — no DtText prop); d-code--sm / d-code-small (below DtText minimum code size) | Check whether uppercase or small code is genuinely required; implement a custom solution or leave as-is |
<!-- dt-text-migrate: review helper --> | d-helper--* — approximated as kind="body" + appropriate density; element IS rewritten | Verify the approximation is visually correct; adjust density or size if needed |
<!-- dt-text-migrate: review composed class on wrapper --> | A rewriteable tag (<div>, <p>, etc.) carrying a composed class that is actually a layout container — it has a layout display class or block/component children, so it isn't a text leaf. Left unchanged. | Move the composed class onto the actual text element inside, or wrap the text in a <dt-text>; keep the container as plain layout |
<!-- dt-text-migrate: review composed class on wrapper tag --> | A composed class on a tag the tool never rewrites — <a>, <button>, <dt-*> components, custom Vue components, etc. Left unchanged. | Decide whether the text inside should become <dt-text>, or apply the equivalent kind/size to the component if it accepts them |
<!-- dt-text-migrate: review nested span --> | A child <span> with directives, events, or extra attributes that can't be safely collapsed into a nested <dt-text> | Migrate the child manually, or keep it as a plain <span> inside the parent <dt-text> |
<!-- dt-text-migrate: review dynamic class --> | :class / v-bind:class bindings containing typography utilities | Convert conditionally bound utilities to conditional props (e.g., :strength="isBold ? 'bold' : 'normal'") |
<!-- dt-text-migrate: review conflicting class --> | A utility class clashes with an explicit prop already on the element (e.g., <dt-text strength="bold" class="d-fw-normal">) | Remove the redundant class; keep the explicit prop |
<!-- dt-text-migrate: review d-fs-N (on-menu — maps to …) --> | A raw d-fs-N font-size class whose size lines up with the tool's legacy kind/size mapping. The comment names the suggested legacy props. | Use the suggested legacy kind + size props, or combine variant="…" with size="N" when you need a composed style plus an exact font-size override |
<!-- dt-text-migrate: review d-fs-N (off-menu — no clean DtText equivalent, keep class) --> | A raw d-fs-N outside the tool's legacy composed-style mapping | If N is a Dialtone font-size token, choose a variant and pair it with size="N". Keep the utility when there is no clear text composition, the value is unsupported, or the class must stay on a non-text wrapper |
<!-- dt-text-migrate: legacy heading — as=… | kind=… | size: … | strength=… | tone=… --> | The hand-rolled heading pattern: an element with both d-fw-* and d-fs-N (no composed class). The comment carries a full proposed migration; kind is headline for <h1>–<h6> and kind=body|label|headline (VERIFY) for ambiguous tags. | Apply the proposed props from the comment; for non-heading tags, confirm the right kind before applying |
The script matches the full composed class name (e.g., d-headline--md) to the mapping table and emits the correct kind, size, and optional density/strength props. Unrecognized classes are kept on the element's class attribute unchanged.
The emitted kind + size form is legacy-compatible. You can leave it as-is, or convert it to the preferred variant form during manual cleanup:
<!-- Tool output, still supported -->
<dt-text as="p" kind="body" size="300">Body copy</dt-text>
<!-- Preferred manual form -->
<dt-text as="p" variant="body-md">Body copy</dt-text>
Non-span tags get an as prop:
<!-- span: no as prop (DtText default) -->
<span class="d-body--sm">Help text</span>
→ <dt-text kind="body" size="100">Help text</dt-text>
<!-- p: as="p" to preserve semantic element -->
<p class="d-body--md">Body copy</p>
→ <dt-text as="p" kind="body" size="300">Body copy</dt-text>
Override utilities are extracted from the class list and written as props on the rewritten element. Any class not recognized as a typography utility is kept on the class attribute.
<!-- Before: mixed composed + override + other class -->
<p class="d-body--md d-fc-tertiary d-truncate d-mb-300">...</p>
<!-- After: typography→props, other class retained -->
<dt-text as="p" kind="body" size="300" tone="tertiary" truncate class="d-mb-300">...</dt-text>
When a direct child <span> of a migrated element carries only recognized typography classes (no extra attributes, directives, or events), the script collapses it into a nested <dt-text>:
<!-- Before -->
<p class="d-headline--md">
<span class="d-fw-bold">Hello</span>
</p>
<!-- After: safe to collapse — span has only recognized classes -->
<dt-text as="p" kind="headline" size="300">
<dt-text strength="bold">Hello</dt-text>
</dt-text>
The script also scans <dt-text> elements that carry residual utility classes from a previous partial migration, and lifts those classes into props:
<!-- Before: already DtText but with leftover utility classes -->
<dt-text kind="body" class="d-fw-bold d-fc-secondary">...</dt-text>
<!-- After -->
<dt-text kind="body" strength="bold" tone="secondary">...</dt-text>
npx dialtone-migrate-typography --dry-run --cwd ./src
npx dialtone-migrate-typography --cwd ./src/components
npx dialtone-migrate-typography --yes
npx dialtone-migrate-typography
In interactive mode the tool processes one file at a time. For each file that has changes, it prints the file path and prompts once before writing:
📄 src/components/Header.vue
Apply? [y]es / [n]o / [a]ll / [q]uit:
Respond with:
y / yes: apply all changes in this filen / no: skip this filea / all: apply this file and every remaining file without further promptsq / quit: stop immediatelyThe prompt applies or skips the whole file — it does not step through individual changes within a file. To preview exactly what would change before committing, run --dry-run first (it reports which files would be modified).
# Single file
npx dialtone-migrate-typography --file src/components/Header.vue --dry-run
# Multiple files
npx dialtone-migrate-typography --file Header.vue --file Footer.vue --yes
When using --file, the --cwd option is ignored.
npx dialtone-migrate-typography --validate --cwd ./src
--validate runs read-only and scans existing <dt-text> elements for prop bugs: object syntax (:kind="{ … }"), invalid prop values, and mixed CSS classes. Useful as a post-migration audit.
npx dialtone-migrate-typography --remove-markers --cwd ./src
Strips all <!-- dt-text-migrate: review … --> comments after you have reviewed and resolved each flagged pattern. Add --dry-run to preview which files would be cleaned.
All options:
| Option | Description |
|---|---|
--cwd <path> | Working directory (default: current directory) |
--file <path> | Specific file to process (repeatable; ignores --cwd) |
--dry-run | Show changes without writing files |
--yes, -y | Apply all changes without prompting |
--remove-markers | Strip all dt-text-migrate review comments |
--validate | Read-only: scan existing <dt-text> for prop bugs |
--package <name> | Package name for injected DtText imports (e.g. @dialpad/dialtone-next). Overrides import-path detection |
--help, -h | Show help |
Files processed: .vue and .html.
When a file gains its first <dt-text>, the script prints a per-file notice on stdout if DtText is not already imported or registered. It infers the import path from the file's existing imports: files that already import from @dialpad/dialtone-vue (or @dialpad/dialtone-icons) get an @dialpad/dialtone-vue suggestion; otherwise it suggests the local @/components/text path. Follow the printed instructions:
Options API (registered globally in your app):
import { DtText } from '@dialpad/dialtone-vue';
export default {
components: { DtText },
// …
};
Composition API:
<script setup>
import { DtText } from '@dialpad/dialtone-vue';
</script>
Projects that import Dialtone components locally will instead see import { DtText } from '@/components/text'; — use whichever path matches your project's existing imports.
If you run Dialtone under a custom package alias (for example @dialpad/dialtone-next while migrating incrementally), pass --package to force injected imports to use it:
npx dialtone-migrate-typography --package @dialpad/dialtone-next --cwd ./src
After resolving all flagged patterns, strip the marker comments:
npx dialtone-migrate-typography --remove-markers --cwd ./src
Run ESLint after migration to fix Vue attribute ordering:
npx eslint --fix "./src/**/*.vue"
d-headline--*, d-body--*, d-label--*, or d-code--md class<dt-text></dt-text>variant when possible, or use the legacy-compatible kind + size mapping in the Class-to-Prop Reference belowas="…" if the original tag is not <span>class attributeUse the size prop for raw font-size token replacement only when it is paired with variant for a complete text composition, or with legacy kind while migrating older code. Do not migrate a raw font-size utility to <dt-text size="…"> by itself.
<!-- Before -->
<p class="d-fs-200">Body text</p>
<!-- After: composed style plus exact font-size token -->
<dt-text as="p" variant="body-md" size="200">Body text</dt-text>
<!-- After: composed style plus exact font-size override -->
<dt-text as="p" variant="body-md" size="250">Large body text</dt-text>
Supported raw size values are 50, 75, 100, 125, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, and 800.
<!-- Before -->
<my-component class="d-body--md d-fc-secondary" />
<!-- After -->
<dt-text kind="body" size="300" tone="secondary">
<my-component />
</dt-text>
For new manual code, use variant instead:
<dt-text variant="body-md" tone="secondary">
<my-component />
</dt-text>
If you own the component, apply the prop directly to the root text element.
Before:
<p :class="{ 'd-fc-secondary': isDimmed }">Copy</p>
After:
<dt-text as="p" :tone="isDimmed ? 'secondary' : undefined">Copy</dt-text>
The table below reflects the migration tool's current legacy-compatible output. For new manual code, prefer variant values such as headline-md, body-md, label-md, and code-sm when they match the intended composed style.
| Old class | kind | size | density | strength | Notes |
|---|---|---|---|---|---|
d-headline--sm / d-headline-small | headline | 100 | — | — | |
d-headline--md / d-headline-medium | headline | 300 | — | — | |
d-headline--lg / d-headline-large | headline | 500 | — | — | |
d-headline--xl / d-headline-extra-large | headline | 600 | — | — | |
d-headline--xxl / d-headline-extra-extra-large | headline | 700 | — | — | |
d-headline--eyebrow / d-headline-eyebrow | — | — | — | — | Flagged — no DtText prop for text-transform: uppercase |
d-headline--sm-soft / d-headline-soft-small | headline | 100 | — | medium | |
d-headline--lg-soft | headline | 500 | — | medium | |
d-headline--sm-compact / d-headline-compact-small | headline | 100 | 200 | — | |
d-headline--md-compact / d-headline-compact-medium | headline | 300 | 300 | — | |
d-headline--lg-compact / d-headline-compact-large | headline | 500 | 200 | — | |
d-headline--xl-compact | headline | 600 | 100 | — | |
d-headline--xxl-compact | headline | 700 | — | — | Same line-height as base |
d-headline--sm-soft-compact / d-headline-compact-soft-small | headline | 100 | 200 | medium | |
d-headline--lg-soft-compact | headline | 500 | 200 | medium | |
d-body--md / d-body-base | body | 300 | — | — | |
d-body--sm / d-body-small | body | 100 | — | — | |
d-body--md-compact / d-body-compact | body | 300 | 300 | — | |
d-body--sm-compact / d-body-compact-small | body | 100 | 200 | — | |
d-label--md / d-label-base | label | 300 | — | — | |
d-label--sm / d-label-small | label | 100 | — | — | |
d-label--md-compact / d-label-compact | label | 300 | 300 | — | |
d-label--sm-compact / d-label-compact-small | label | 100 | 200 | — | |
d-label--md-plain / d-label-plain | label | 300 | — | normal | |
d-label--md-plain-compact / d-label-compact-plain | label | 300 | 300 | normal | |
d-label--sm-plain / d-label-plain-small | label | 100 | — | normal | |
d-label--sm-plain-compact / d-label-compact-plain-small | label | 100 | 200 | normal | |
d-code--md / d-code-base | code | 200 | — | — | |
d-code--sm / d-code-small | — | — | — | — | Flagged — below DtText minimum code size |
d-helper--md / d-helper-base | body | 300 | 300 | — | Flagged for review — approximated |
d-helper--sm / d-helper-small | body | 100 | 200 | — | Flagged for review — approximated |
| Old class | DtText prop | Value |
|---|---|---|
d-fw-bold | strength | "bold" |
d-fw-semibold | strength | "semibold" |
d-fw-medium | strength | "medium" |
d-fw-normal | strength | "normal" |
d-lh-100 | density | "100" |
d-lh-200 | density | "200" |
d-lh-300 | density | "300" |
d-lh-400 | density | "400" |
d-lh-500 | density | "500" |
d-lh-600 | density | "600" |
d-fc-primary | tone | "primary" |
d-fc-secondary | tone | "secondary" |
d-fc-tertiary | tone | "tertiary" |
d-fc-muted | tone | "muted" |
d-fc-disabled | tone | "disabled" |
d-fc-placeholder | tone | "placeholder" |
d-fc-critical | tone | "critical" |
d-fc-critical-strong | tone | "critical-strong" |
d-fc-positive | tone | "positive" |
d-fc-positive-strong | tone | "positive-strong" |
d-fc-warning | tone | "warning" |
d-fc-info | tone | "info" |
d-fc-info-strong | tone | "info-strong" |
d-fc-neutral-black | tone | "neutral-black" |
d-fc-neutral-white | tone | "neutral-white" |
d-truncate | truncate | (boolean prop — no value) |
d-ta-left | align | "start" |
d-ta-right | align | "end" |
d-ta-center | align | "center" |
d-ta-justify | align | "justify" |
d-ff-mono | kind | "code" |
Typography Utilities to DtText documentation last updated Friday, September 4, 2026
fix/popover-modal-zindex-scope