Enterprise dashboard with data tables and admin UI
9 min read Vue 3 Migration

Element UI to Element Plus: A Practical Migration Map for Enterprise Dashboards

Element (Vue 2) powers thousands of B2B consoles. Element Plus (Vue 3) is the supported path, but the jump is not a find-and-replace. Here is the map we use so table-heavy screens, wizards, and popovers survive contact with real users.

Unlike a marketing site, enterprise dashboards are dense with el-table, el-form, and bespoke cell renderers. The Vue 3 work ( core checklist) and the library work interleave. Pinia and state migration often land in the same sprints as table pagination refactors because the same screens drive both.

This post stays specific to Element / Element Plus, so we are not re-covering the generic Bootstrap or Vuetify angles covered in other articles on this site.

1. Inventory: components that hurt

Before touching Vue core, we tag screens by the Element controls they use. In practice, the time sinks are: el-table with custom column slots, nested dialogs + drawer stacks, el-form with async rules, and global $message / $notify usage (imperative APIs you will trace through every file).

2. i18n and day-one locale parity

If you ship in multiple languages, your Element Plus locale config must be paired with app-level i18n so strings and date formats stay aligned. A partial migration (English Plus + legacy Vue 2 in another locale) is a support trap—treat i18n as a release gate, not a follow-up.

3. Rollout strategy

We prefer vertical slices: migrate a domain (billing, org admin, reporting) to Vue 3 with Element Plus end-to-end, with feature flags behind routes. If you federate or run multiple deployables, align package versions per shell so you do not ship two major Element lineages at once.

4. What “done” means for QA

  • Print / PDF flows where el-table width math changed
  • Keyboard and focus order through nested overlays
  • Row virtualization or infinite scroll, if you brought third-party add-ons in Vue 2

5. The component-by-component diff

Most Element UI components have a near-identical Element Plus counterpart, but the breaking changes cluster in a predictable set. Here is the cheat sheet we hand to teams on day one:

Element UI (Vue 2)Element Plus (Vue 3)What breaks
this.$messageElMessage importImperative API moves from Vue prototype to named import; every call site changes.
el-table scoped slot slot-scope="scope"#default="scope"Slot syntax change; scoped slots renamed.
el-form prop path stringsSame syntaxValidation timing changed — async rules now resolve through Promises only.
el-dialog with .sync on visiblev-modelNo more .sync; converts cleanly with codemod.
el-input v-model.trimSameWorks, but composition events fire differently with IME (Japanese, Chinese).
el-select remote with remote-methodSame prop namesDebounce default changed; check loading flicker.
el-date-picker default valueel-date-pickerDefault returns Date object; formerly returned a localized string in some configs.

6. Migrating el-table without losing your mind

el-table is where most enterprise Element migrations stall. The breaking changes look small in the docs and large in production, especially around scoped slots and column rendering.

Before (Vue 2 / Element UI)

<el-table :data="rows">
  <el-table-column label="Status">
    <template slot-scope="scope">
      <status-badge :value="scope.row.status" />
    </template>
  </el-table-column>
</el-table>

After (Vue 3 / Element Plus)

<el-table :data="rows">
  <el-table-column label="Status">
    <template #default="scope">
      <status-badge :value="scope.row.status" />
    </template>
  </el-table-column>
</el-table>

The mechanical change is trivial. The traps are:

  • Render functions in column definitions using render-header or scopedSlots generators must be rewritten. They were usually copy-pasted from a senior dev's gist three years ago and nobody knows why.
  • Custom sort and filter functions receive arguments in slightly different shapes. Snapshot tests catch this; visual review does not.
  • Fixed columns (fixed="left") now use CSS position: sticky. Older browsers and print styles need verification.
  • Virtualized tables in Element Plus use el-table-v2 with a different API; this is a port, not an upgrade.

7. Imperative APIs and the $message problem

Element UI patched the Vue prototype: this.$message, this.$notify, this.$confirm, this.$msgbox. Element Plus exports them as named imports. That is correct, but it means every call site changes.

// before
this.$message.success("Saved");

// after
import { ElMessage } from "element-plus";
ElMessage.success("Saved");

Two pragmatic options:

  • Codemod everything. A jscodeshift script handles the simple cases; the awkward cases (e.g. this.$confirm(...).then(...)) need a hand-pass.
  • Wrap with a thin facade. Re-export the imperative APIs from @/lib/notify.ts, then import from there everywhere. Future moves (away from Element entirely, for example) become a one-file change.

8. Theming, dark mode, and CSS variables

Element UI used SCSS variables compiled into a single theme. Element Plus uses CSS custom properties, which makes runtime theming and dark mode straightforward — but every legacy SCSS override file must be revisited.

  • Replace $--color-primary overrides with --el-color-primary CSS variables.
  • Move dark-mode toggles to the html.dark selector recommended by Element Plus.
  • If you use a corporate design token package, audit which tokens map to which Element Plus variables. This is the cleanest moment to consolidate; coordinate with any design system extraction work in progress.

9. Common pitfalls in enterprise dashboards

  • Print stylesheets break silently. CSS variable cascading interacts with @media print in ways that flat SCSS variables did not. QA your PDF and "print to PDF" flows.
  • Form validation timing. Async rules resolved synchronously in some Element UI corners. Element Plus is strict about Promises. Race conditions with debounced server-side checks surface immediately.
  • Locale defaults. Element UI fell back to English when no locale was registered. Element Plus warns and ships untranslated keys. Pair locale registration with vue-i18n bootstrap.
  • el-popover teleport target. Default changed; popovers may render inside scrolled containers in unexpected places. Set teleported="false" selectively.
  • Auto-import + global styles double up. If you mix unplugin-vue-components auto-import with a global Element Plus import, CSS bundles inflate. Pick one strategy.

10. Step-by-step migration plan

  1. Inventory. Grep every el-* tag and $message / $notify / $confirm call. Tag screens by complexity (table-heavy, form-heavy, simple).
  2. Wrap imperative APIs. Introduce @/lib/notify.ts in the Vue 2 codebase first, so the cutover is mechanical.
  3. Pick a vertical slice. One domain, one route group, one feature flag. We typically start with an internal admin screen — production traffic, low blast radius.
  4. Run codemods on the slice. slot-scope#default, .syncv-model, prototype methods → named imports.
  5. Hand-pass el-table columns. This is the one place codemods reliably miss edge cases.
  6. QA pass. Print/PDF, keyboard order, IME input, locale parity. Add Cypress component tests where coverage is thin — see our Cypress component testing post.
  7. Soak in production behind a flag for one release cycle. Compare error rates against the Vue 2 baseline.
  8. Move to the next slice. Track velocity per slice; estimates compound, and the second slice should be ~30% faster than the first.

11. Anti-patterns we see

  • "Migrate Element first, then Vue." Element Plus does not run on Vue 2. Element UI does not run on Vue 3. They are coupled migrations.
  • Forking Element UI to add Vue 3 support. Some teams attempt this. The maintenance cost dwarfs the migration cost within 6 months.
  • Big-bang switch over a weekend. Enterprise dashboards have long-tail behaviors (saved filters, exported PDFs, scheduled jobs that hit the same UI). Slice migrations win.
  • Skipping the wrapper layer for imperative APIs. Direct ElMessage imports everywhere couples your code tighter to Element Plus than necessary.
  • Treating @vue/compat as the bridge. Compat does not help with Element — both libraries must move together. Coordinate this with the broader strangler fig pattern.

FAQ

How long does a typical Element UI to Element Plus migration take?

For a 50-screen dashboard, 8–14 weeks of focused work for a team of 3–4 engineers, including the parallel Vue 3 core migration. Our cost estimate guide breaks down the variables.

Can we use Element Plus for new features while keeping Element UI for legacy screens?

Only across separate apps or micro-frontends. In one Vue runtime you must pick one. Loading both inflates your bundle and creates CSS conflicts.

Should we switch to a different UI kit during the migration?

If your product is Element-heavy, no — staying on Element Plus is the lowest-risk path. If you were already considering a switch, doing it post-migration (on a stable Vue 3 baseline) is cheaper than doing both at once.

What about Element Plus accessibility?

Better than Element UI in most components, but not perfect. Audit keyboard navigation in dialogs, tables, and select dropdowns. Some teams pair the migration with their first real WCAG sweep.

Element-heavy app and a hard deadline?

We have migrated data-dense UIs on Element to Vue 3 with testable slices and predictable calendars.

Discuss your dashboard

Conclusion

Element Plus is the right successor for most Element UI codebases, but the migration effort tracks how deeply your product relied on table and form composition—not how many el-button tags you have. Sequence the painful surfaces early and you will de-risk the rest of the Vue 3 work.

Related guides