TLDR
- Component
sizeprops now favor numeric values, e.g.100(xs),200(sm),300(md),400(lg),500(xl). - T-shirt aliases still work — this is not a breaking change, and are marked to be sunset.
- Use the ESLint rule or migration script to update your code.
- Unchanged: Icon sizes.
Why Numeric?
- Extensible. Need a size between
smandmd? Use250. No naming gymnastics like "smedium." - Consistent. Same 100-unit convention as
dt-spacing-*,dt-layout-*,dt-icon-size-*, and color scales. - Predictable for humans, engineers, and LLMs. Bigger number = bigger size. LLMs will have an easier time reasoning about ordinal relationships without needing a lookup table. T-shirt labels can't be consistently interpolated or composed by humans or AI.
Examples
Before
<dt-button size="sm">
...
</dt-button>
<dt-text kind="headline" size="xl">
...
</dt-text>
<dt-input size="lg" label="Name" />
After
<dt-button :size="200">
...
</dt-button>
<dt-text kind="headline" :size="500">
...
</dt-text>
<dt-input :size="400" label="Name" />
Size Mapping
| T-shirt | Numeric | Description |
|---|---|---|
xs | 100 | Extra small |
sm | 200 | Small |
md | 300 | Medium (default) |
lg | 400 | Large |
xl | 500 | Extra large |
2xl | 600 | Text headline only |
3xl | 700 | Text headline only |
What's Affected
DtButton, DtInput, DtSelectMenu, DtSegmentedControl, DtText, DtToggle, DtChip, DtCodeblock, DtEmptyState, DtSkeleton, DtSplitButton, DtFilterPill, DtCombobox, DtComboboxMultiSelect, DtComboboxWithPopover, DtTabGroup
What's NOT Affected
- Icon sizes (
dt-icon,dt-loader,dt-emoji,dt-progress-circle) — already numeric, unchanged. - DtAvatar — already migrated to numeric in a prior release.
- CSS utility classes — no changes. Numeric prop values map to the same CSS classes as before.
ESLint Rule
deprecated-tshirt-sizes flags t-shirt size usage on Dialtone components and auto-fixes to numeric.
Add to your ESLint config:
// eslint.config.js (flat config)
import dialtone from '@dialpad/eslint-plugin-dialtone';
export default [
{
plugins: { dialtone },
rules: {
'dialtone/deprecated-tshirt-sizes': 'warn',
},
},
];
Then run:
npx eslint --fix "src/**/*.vue"
The rule handles: size, label-size, labelSize, and speed props on dt-* and Dt* components.
Skipped: Dynamic bindings (:size="computedValue"), non-Dialtone components.
Migration Script
dialtone-migrate-tshirt-to-numeric batch-transforms t-shirt sizes to numeric across your codebase.
Preview Changes
npx dialtone-migrate-tshirt-to-numeric --dry-run --cwd ./src
Apply Changes
npx dialtone-migrate-tshirt-to-numeric --cwd ./src
Apply All Without Prompting
npx dialtone-migrate-tshirt-to-numeric --yes --cwd ./src
The script handles:
.vue,.md,.html,.js,.ts,.jsx,.tsxfiles- Multiline component tags
- Any prop ending in
size,Size,speed, orSpeed(future-proof)
Skipped:
- Already v-bound props (
:size="...") - Non-Dialtone components
- Non-scale size props (
button-width-size,background-size,font-size)
Dynamic Bindings
The ESLint rule flags t-shirt literals inside dynamic bindings (:size="'sm'", :size="condition ? 'sm' : 'md'"), but cannot auto-fix them. The migration script skips dynamic bindings entirely. Both require manual review.
Ternary expressions
Before
<dt-button :size="isCompact ? 'sm' : 'md'" />
After
<dt-button :size="isCompact ? 200 : 300" />
Computed properties
Before
computed: {
buttonSize () {
return this.isLarge ? 'lg' : 'md';
},
}
After
computed: {
buttonSize () {
return this.isLarge ? 400 : 300;
},
}
Lookup maps
Before
const sizeMap = {
compact: 'sm',
default: 'md',
spacious: 'lg',
};
After
const sizeMap = {
compact: 200,
default: 300,
spacious: 400,
};
Props from parent
If a parent component passes a t-shirt string to a child's size prop, update the parent — the child already accepts numeric.