TLDR
- Use DtStack in place of Flex CSS Utilities.
- Use the Migration Tool to replace
class="d-d-flex"to<dt-stack>.
Overview
DtStack is a primitive layout component using flexbox for simple vertical or horizontal layouts. It should be the first tool you reach for. It handles direction, alignment, justification, and gap through props instead of utility classes. This reduces class clutter, improves intent, and improves readability.
A Migration Tool is available to migrate class="d-d-flex" to <dt-stack>, or you may do so manually.
Why Use the Migration Tool?
The migration tool automates the conversion process with several key benefits:
- Speed: Migrate entire projects in minutes instead of hours
- Consistency: Ensures uniform conversion patterns across all files
- Safety: Automatically detects edge cases that would break if migrated
- Accuracy: No manual transcription errors or forgotten conversions
- Visibility: Color-coded diffs and warnings help you review changes
The tool is ideal for projects with many flex containers. For small, one-off changes, manual migration may be faster.
Examples
Before
<div
class="
d-d-flex
d-ai-center
d-jc-space-between
d-g16
"
>
<div>Left</div>
<div>Right</div>
</div>
After
<dt-stack
direction="row"
align="center"
justify="space-between"
gap="500"
>
<div>Left</div>
<div>Right</div>
</dt-stack>
Before
<div class="d-d-flex d-fd-column d-g24">
<div>First</div>
<div>Second</div>
<div>Third</div>
</div>
After
<dt-stack gap="550">
<div>First</div>
<div>Second</div>
<div>Third</div>
</dt-stack>
Migration Tool: Flex to Stack
dialtone-migrate-flex-to-stack scans Vue files for d-d-flex patterns and converts them to DtStack. It's included with @dialpad/dialtone-css.
The tool migrates:
- Native HTML elements (
div,span,section, etc.) with Flex CSS utilities, e.g.d-d-flex
Requires manual migration:
- Custom Vue components (e.g.,
<my-component class="d-d-flex">) - Responsive utilities (e.g.,
md:d-ai-center) - Dynamic
:classbindings (e.g.,:class="{ 'd-d-flex': condition }")
The migration tool will log warnings for dynamic class bindings that contain flex utilities.
How the Tool Works
Direction Handling
The migration tool automatically adds direction="row" to match flexbox's default behavior. CSS flexbox defaults to horizontal layout (flex-direction: row), while DtStack defaults to vertical layout (direction="column").
No direction utility (adds row):
Before
<div class="d-d-flex d-ai-center">
<span>Item 1</span>
<span>Item 2</span>
</div>
After
<dt-stack direction="row" align="center">
<span>Item 1</span>
<span>Item 2</span>
</dt-stack>
Explicit column (omits redundant prop):
Before
<div class="d-d-flex d-fd-column d-g16">
<span>Item 1</span>
<span>Item 2</span>
</div>
After
<!-- direction="column" is omitted - it's DtStack's default -->
<dt-stack gap="500">
<span>Item 1</span>
<span>Item 2</span>
</dt-stack>
Semantic HTML Preservation
For non-div elements, the tool automatically adds an as prop to preserve semantic meaning:
<!-- Before -->
<section class="d-d-flex d-g16">
<h2>Section Title</h2>
<p>Content</p>
</section>
<!-- After -->
<dt-stack as="section" gap="500">
<h2>Section Title</h2>
<p>Content</p>
</dt-stack>
The tool handles all semantic HTML elements (section, article, aside, nav, header, footer, etc.) automatically.
Edge Cases & Special Handling
The migration tool intelligently handles various edge cases:
Classes Removed:
d-d-flex- Replaced by the<dt-stack>component itselfd-fl-center- Converted to props (align="center"+justify="center")
Automatically Converted:
d-fl-center- Setsdisplay: flex,align-items: center, andjustify-content: center. Converts to DtStack with appropriate props.
Skipped (with warnings):
d-fl-col*- Deprecated flex column systemd-stack*,d-flow*- Auto-spacing utilities (margin-based, incompatible with gap)d-d-inline-flex- Inline flex containers (DtStack is block-level only)- Elements with
refattributes used for DOM manipulation (see Ref Attributes below) - Dynamic
:classbindings containing flex utilities (see Dynamic Class Bindings)
Retained as Classes:
Some utilities remain as classes because they don't have DtStack prop equivalents:
Flex Properties:
d-fw-*(flex-wrap: wrap, nowrap)d-fl-grow*,d-fl-shrink*(flex-grow, flex-shrink)d-as-*(align-self)d-ac-*(align-content)d-order*(order)
Grid/Flex Hybrids:
d-ji-*(justify-items)d-js-*(justify-self)d-plc-*(place-content)d-pli-*(place-items)d-pls-*(place-self)
Deprecated (with warnings):
d-flg*(deprecated flex gap) - Tool suggests usingd-g*instead
Other:
- Large gaps (
d-g80,d-g96, etc.) - No DtStack gap prop equivalent aboved-g64
Ref Attributes
Elements with ref attributes that are used for DOM manipulation are automatically skipped. When a native element is converted to a Vue component, the ref returns a component instance instead of a DOM element. This breaks code that expects DOM APIs.
Example of what gets skipped:
<!-- This element will be SKIPPED because containerRef is used with addEventListener -->
<div ref="containerRef" class="d-d-flex d-ai-center">
...
</div>
<script setup>
const containerRef = ref(null);
onMounted(() => {
containerRef.value.addEventListener('click', handler); // DOM API usage detected
});
</script>
DOM APIs that trigger skip detection:
- Event listeners:
addEventListener,removeEventListener - DOM queries:
querySelector,querySelectorAll,closest,contains - Measurements:
getBoundingClientRect,offsetWidth,clientHeight, etc. - Focus management:
focus,blur,click - Scrolling:
scrollIntoView,scrollTo - Style/attribute manipulation:
classList,setAttribute,style - DOM traversal:
parentNode,children,nextSibling, etc.
Manual fix: If you need to convert an element with a ref, update your code to use .$el:
// Before (native element ref)
containerRef.value.focus();
// After (component ref)
containerRef.value.$el.focus();
Usage
Preview Changes
npx dialtone-migrate-flex-to-stack --dry-run
Target a Directory
npx dialtone-migrate-flex-to-stack --cwd ./src/components
Apply All Changes
npx dialtone-migrate-flex-to-stack --yes
Interactive Mode
npx dialtone-migrate-flex-to-stack
Interactive mode displays each change with:
- Color-coded diff (red = before, green = after)
- List of retained classes with explanations
- Warnings for edge cases
For each match, respond with:
yoryes: Apply this changenorno: Skip this changeaorall: Apply all remaining changes without further promptsqorquit: Stop and save
At the end, you'll see a summary showing files scanned, modified, and total changes applied.
Options:
--dry-run: Show changes without applying--validate: Validate transformations and check for potential issues (implies--dry-run)--cwd <path>: Set working directory--ext <ext>: File extension to process (default:.vue). Can be specified multiple times--file <path>: Specific file to process. Can be specified multiple times. Relative or absolute paths supported--yesor-y: Auto-apply all changes--show-outline: Add attribute for visual debugging--remove-outline: Remove attributes after review--helpor-h: Show help
Process specific file extensions:
# Process only markdown files (useful for documentation sites)
npx dialtone-migrate-flex-to-stack --ext .md --cwd ./docs --dry-run
# Process both .vue and .md files
npx dialtone-migrate-flex-to-stack --ext .vue --ext .md --yes
Target specific files:
# Single file
npx dialtone-migrate-flex-to-stack --file src/components/Header.vue --dry-run
# Multiple files
npx dialtone-migrate-flex-to-stack --file Header.vue --file Footer.vue --yes
# Absolute path
npx dialtone-migrate-flex-to-stack --file /absolute/path/to/Component.vue
When using --file, the --cwd option is ignored. The --ext option still validates file extensions.
Validate transformations before applying:
# Run validation to check for potential issues
npx dialtone-migrate-flex-to-stack --validate
# Validate specific directory
npx dialtone-migrate-flex-to-stack --validate --cwd ./src/components
Validation mode checks for:
- Missing closing tags for transformed elements
- Invalid transformation positions
- Overlapping transformations
- Large gaps between opening and closing tags (potential mismatch)
If validation passes, you can confidently run without --validate to apply changes.
Post-Migration
After running the migration tool, use ESLint to fix Vue attribute ordering. The migration tool preserves the original attribute order, but Vue style guide recommends directives like v-if come before other attributes.
# Run migration
npx dialtone-migrate-flex-to-stack --yes
# Fix attribute ordering with ESLint
npx eslint --fix "./src/**/*.vue"
Dialtone now enables the vue/attributes-order rule to ensure Vue style guide compliance. Directives like v-if, v-for, and v-show will be moved before props and classes.
Flex Utilities Support
Flex CSS Utilities are still supported on DtStack, e.g. some flex utilities have no DtStack prop equivalent or there's an intentional use of a utility class.
Examples
d-fw-wrap,d-fw-nowrap(flex-wrap)d-fl-grow1,d-fl-shrink0(flex grow/shrink)d-as-*(align-self)d-ac-*(align-content)d-g*values larger thand-g64(700)
For example, d-fw-wrap isn't a DtStack prop, but can still be applied.
<dt-stack direction="row" align="center" gap="400" class="d-fw-wrap">
...
</dt-stack>
Manual Migration
Native HTML Elements
- Find elements with
d-d-flexclass - Change the tag to
<dt-stack> - Change closing tag to
</dt-stack> - Convert classes to props using the Flex to Stack Reference below
- Keep non-convertible classes on the component
- Remove
d-d-flex(DtStack is already flex)
Custom Components
Option 1: Wrap the component
<!-- Before -->
<my-component class="d-d-flex d-ai-center d-g16" />
<!-- After -->
<dt-stack align="center" gap="500">
<my-component />
</dt-stack>
Option 2: Update the component internally
If you own the component, refactor its root element to use DtStack.
Responsive Patterns
Before:
<div class="d-d-flex d-ai-flex-start md:d-ai-center lg:d-jc-between">
After:
<dt-stack
:align="{ default: 'start', md: 'center' }"
:justify="{ lg: 'space-between' }"
>
Dynamic Class Bindings
Before:
<div ... :class="{ 'd-ai-center': condition }">
After: remap to align prop
<dt-stack ... :align="condition ? 'center' : 'start'">
Before:
<div :class="{ 'd-d-flex d-ai-center': isActive }">
After: (Option 1 - conditional rendering)
<dt-stack v-if="isActive" align="center">
After: (Option 2 - keep some classes dynamic)
<dt-stack :class="{ 'd-ai-center': isActive }">