Skip to content

Migrate from v1 to v2

Overview

v2.0.0 is a major release with several breaking changes. Here is a summary of the key changes:

  • 6 components removed/renamed: Typewriter, XMarkdown, XMarkdownAsync, Sender, MentionSender, EditorSender are no longer bundled
  • 2 new components: XSender, ConfigProvider
  • New theme system: Based on --elx-* CSS variables, with light/dark mode support
  • Hooks changes: Removed usePrism, added useNamespace and useTheme; XRequest is deprecated
  • Markdown rendering extracted to external package: x-markdown-vue

Migration Advice

Before starting the migration, make sure to back up your project. It is recommended to complete the migration in a development environment first, and deploy to production only after confirming everything works correctly.

Quick Checklist

Migration ItemTypeImpactDetails
TypewriterRemovedPages using typewriter effectx-markdown-vue + enable-animate
XMarkdownRemovedMarkdown renderingMarkdownRenderer from x-markdown-vue
XMarkdownAsyncRemovedAsync Markdown renderingMarkdownRenderer from x-markdown-vue
SenderRenamedInput componentXSender
MentionSenderRenamedMention input componentXSender + mentionConfig
EditorSenderRenamedRich text input componentXSender (minor API changes, requires plugin version > 1.3.98)
usePrismRemovedCode highlighting hook→ Built-in Shiki in x-markdown-vue
XRequestDeprecatedSSE requests→ Recommended: hook-fetch
useSendNo changesSend state management hookContinue using as-is
useRecordNo changesSpeech recognition hookContinue using as-is
useXStreamNo changesSSE streaming hookContinue using as-is
useNamespaceAddedInternal BEM namespace hook
useThemeAddedTheme variable management hook
ConfigProviderAddedGlobal theme and dark mode configuration
BubbleList.triggerIndicesRemovedPages using this propRemoved from source, no migration needed
BubbleList.@completeRemovedPages listening to this eventRemoved from source, no migration needed
CSS VariablesChangedCustom styles--elx-* prefix

Upgrade Dependencies

Version Requirements

DependencyMinimum VersionNotes
Vue>= 3.5.17v2 requires newer Vue reactivity features
Element Plus>= 2.9.7Components depend on new Element Plus features
Node.js>= 18.x (recommended >= 20.x)Development environment

Upgrade Commands

sh
pnpm update vue-element-plus-x@latest
pnpm update vue@">=3.5.17"
pnpm update element-plus@">=2.9.7"
sh
npm install vue-element-plus-x@latest
npm install vue@">=3.5.17"
npm install element-plus@">=2.9.7"
sh
yarn add vue-element-plus-x@latest
yarn add vue@">=3.5.17"
yarn add element-plus@">=2.9.7"

Install x-markdown-vue (if using Markdown rendering)

If your project uses Typewriter, XMarkdown, or XMarkdownAsync, install the standalone package:

sh
pnpm add x-markdown-vue
pnpm add katex
pnpm add shiki shiki-stream
sh
npm install x-markdown-vue
npm install katex
npm install shiki shiki-stream
sh
yarn add x-markdown-vue
yarn add katex
yarn add shiki shiki-stream

💌 Info

  • katex: For LaTeX math formula rendering (optional)
  • shiki / shiki-stream: For code syntax highlighting (recommended)

Removed Components

Typewriter → x-markdown-vue + enable-animate

The built-in Typewriter component has been removed in v2.0.0. Use MarkdownRenderer from x-markdown-vue with the enable-animate prop instead.

vue
<template>
  <Bubble :avatar="avatar" placement="start">
    <template #content>
      <Typewriter :content="content" />
    </template>
  </Bubble>
</template>

<script setup>
import { Bubble, Typewriter } from 'vue-element-plus-x';
</script>
vue
<template>
  <Bubble :avatar="avatar" placement="start">
    <template #content>
      <MarkdownRenderer :markdown="content" :enable-animate="true" />
    </template>
  </Bubble>
</template>

<script setup>
import { Bubble } from 'vue-element-plus-x';
import { MarkdownRenderer } from 'x-markdown-vue';
import 'x-markdown-vue/style';
</script>

XMarkdown / XMarkdownAsync → MarkdownRenderer

XMarkdown and XMarkdownAsync are no longer bundled in v2.0.0. Use MarkdownRenderer from the standalone x-markdown-vue package.

vue
<template>
  <XMarkdown :content="content" />
</template>

<script setup>
import { XMarkdown } from 'vue-element-plus-x';
</script>
vue
<template>
  <MarkdownRenderer :markdown="content" />
</template>

<script setup>
import { MarkdownRenderer } from 'x-markdown-vue';
import 'x-markdown-vue/style';
</script>

Sender / MentionSender → XSender

Sender and MentionSender have been consolidated into XSender. Mention functionality is now configured through the mentionConfig prop.

vue
<template>
  <Sender v-model="value" placeholder="Type a message..." />
</template>

<script setup>
import { ref } from 'vue';
import { Sender } from 'vue-element-plus-x';

const value = ref('');
</script>
vue
<template>
  <XSender v-model="value" placeholder="Type a message..." />
</template>

<script setup>
import { ref } from 'vue';
import { XSender } from 'vue-element-plus-x';

const value = ref('');
</script>

MentionSender Migration

vue
<template>
  <MentionSender
    v-model="value"
    placeholder="@mention a user..."
    :mention-config="mentionConfig"
  />
</template>
vue
<template>
  <XSender
    v-model="value"
    placeholder="@mention a user..."
    :mention-config="mentionConfig"
  />
</template>

EditorSender → XSender

💌 Info

EditorSender has been renamed to XSender with minor API changes. Make sure the underlying plugin version is greater than 1.3.98.

vue
<template>
  <EditorSender v-model="value" />
</template>

<script setup>
import { ref } from 'vue';
import { EditorSender } from 'vue-element-plus-x';

const value = ref('');
</script>
vue
<template>
  <XSender v-model="value" variant="default" />
</template>

<script setup>
import { ref } from 'vue';
import { XSender } from 'vue-element-plus-x';

const value = ref('');
</script>

BubbleList Changes

triggerIndices Prop Removed

The triggerIndices prop has been removed from the source code in v2.0.0. Please remove any related bindings from your templates.

vue
<BubbleList :list="list" :trigger-indices="'all'" />
vue
<BubbleList :list="list" />

@complete Event Removed

The @complete event has been removed from the source code in v2.0.0. Please remove any related listeners from your templates.

vue
<BubbleList :list="list" @complete="onComplete" />
vue
<BubbleList :list="list" />

💡 Tip

In v2, the typing effect is no longer controlled by BubbleList. Instead, use the #content slot with MarkdownRenderer's enable-animate prop. In streaming scenarios, simply concatenate characters returned by the backend into item.content, and MarkdownRenderer will automatically render incrementally.

Markdown Rendering Migration (#content Slot)

Since triggerIndices and @complete have been removed, v2 recommends using the #content slot with MarkdownRenderer for typewriter effects and Markdown rendering:

vue
<template>
  <BubbleList :list="list" :trigger-indices="'all'" @complete="onComplete" />
</template>

<script setup>
import { BubbleList } from 'vue-element-plus-x';

const list = ref([
  { content: 'Hello!', placement: 'start', role: 'assistant' }
]);

const onComplete = (instance, index) => {
  console.log('Typing complete:', index);
};
</script>
vue
<template>
  <BubbleList :list="list">
    <template #content="{ item }">
      <MarkdownRenderer :markdown="item.content" :enable-animate="true" />
    </template>
  </BubbleList>
</template>

<script setup>
import { ref } from 'vue';
import { BubbleList } from 'vue-element-plus-x';
import { MarkdownRenderer } from 'x-markdown-vue';
import 'x-markdown-vue/style';

const list = ref([
  { content: 'Hello!', placement: 'start', role: 'assistant' }
]);
</script>

Streaming Rendering: Concatenating SSE Chars with hook-fetch

In v2, the recommended approach for streaming APIs (SSE) is: use hook-fetch to receive streaming data from the backend, concatenate each chunk of characters into the corresponding bubble's content field, and MarkdownRenderer will automatically render incrementally.

vue
<template>
  <BubbleList :list="list">
    <template #content="{ item }">
      <MarkdownRenderer :markdown="item.content" :enable-animate="true" />
    </template>
  </BubbleList>
  <XSender v-model="inputValue" @submit="handleSend" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { BubbleList, XSender } from 'vue-element-plus-x';
import { useHookFetch } from 'hook-fetch/vue';
import { sseTextDecoderPlugin } from 'hook-fetch/plugins/sse';
import { MarkdownRenderer } from 'x-markdown-vue';
import 'x-markdown-vue/style';

interface MessageItem {
  role: 'user' | 'assistant';
  content: string;
  placement: 'start' | 'end';
}

const list = ref<MessageItem[]>([]);
const inputValue = ref('');

async function handleSend() {
  const userMsg = inputValue.value.trim();
  if (!userMsg) return;

  // Add user message
  list.value.push({ role: 'user', content: userMsg, placement: 'end' });

  // Add an empty AI message placeholder
  const aiIndex = list.value.length;
  list.value.push({ role: 'assistant', content: '', placement: 'start' });
  inputValue.value = '';

  // Use hook-fetch to make an SSE streaming request
  const { stream } = useHookFetch({
    url: '/api/chat',
    method: 'POST',
    body: JSON.stringify({ message: userMsg }),
    plugins: [sseTextDecoderPlugin()]
  });

  // Concatenate streaming chunks into the corresponding bubble's content
  for await (const chunk of stream()) {
    list.value[aiIndex].content += chunk.result ?? '';
  }
}
</script>

💌 Info

  • sseTextDecoderPlugin decodes the SSE text stream — chunk.result is the parsed text fragment for each chunk
  • Simply keep appending fragments to item.content, and MarkdownRenderer will handle incremental rendering and the typewriter effect automatically
  • For more details on hook-fetch, see the hook-fetch documentation

Hooks Changes

usePrism Removed

usePrism has been removed in v2. Code highlighting is now provided by Shiki, which is built into x-markdown-vue — no additional configuration needed.

useNamespace (Added)

An internal BEM namespace hook that provides CSS class name generation and variable binding. Generally, you don't need to use this directly in application code.

useTheme (Added)

A theme variable management hook that provides reactive properties like isDark and themeOverrides. Typically used indirectly through the ConfigProvider component.

useRecord (No Changes)

Speech recognition hook. API remains unchanged — you can continue using it as-is.

useXStream (No Changes)

SSE streaming data processing hook. API remains unchanged — you can continue using it as-is.

useSend (No Changes)

Frontend loading state management hook, independent of the request library. API remains unchanged — you can continue using it as-is.

XRequest Deprecated

XRequest has been deprecated. hook-fetch is recommended as a replacement.

ts
import { XRequest } from 'vue-element-plus-x';

const request = new XRequest({
  baseURL: '/api',
  onMessage: msg => {
    // Handle streaming messages
  }
});
request.send('/chat');
ts
import { useHookFetch } from 'hook-fetch';

const { data, loading, send } = useHookFetch({
  url: '/api/chat'
  // See hook-fetch documentation
});

💌 Info

The useSend hook is still available. It is a wrapper for frontend loading state management and is independent of the request library.

New: ConfigProvider Theme System

Basic Usage

ConfigProvider is a new global configuration component in v2 that controls theme mode and overrides theme variables.

vue
<template>
  <ConfigProvider :theme="theme">
    <App />
  </ConfigProvider>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { ConfigProvider } from 'vue-element-plus-x';

const theme = ref<'light' | 'dark'>('light');
</script>

Dark Mode

Set theme to 'dark' to enable dark mode. When enabled, a dark class is added to the html element.

vue
<template>
  <ConfigProvider :theme="isDark ? 'dark' : 'light'">
    <App />
  </ConfigProvider>
</template>

themeOverrides

Override --elx-* CSS variables via the themeOverrides prop:

vue
<script setup lang="ts">
const themeOverrides = {
  common: {
    'color-primary': '#7c3aed'
  },
  components: {
    Welcome: {
      'welcome-filled-bg': '#f0f9ff',
      'welcome-title-color': 'rgba(0, 0, 0, 0.88)'
    }
  }
};
</script>

💡 Tip

For the full list of theme variables, see Theme Tokens.

CSS / Style Changes

--elx-* Variable Prefix

v2 uses --elx-* as the unified theme CSS variable prefix, distinct from Element Plus's --el-* prefix.

ConfigurationMapping
themeOverrides.common.color-primary--elx-color-primary
themeOverrides.common.bg-page--elx-bg-page
themeOverrides.components.Welcome.welcome-filled-bg--elx-welcome-filled-bg

📌 Warning

The namespace prop only affects component class name prefixes. It does not affect theme CSS variable prefixes. Theme variables are always prefixed with --elx-*.

Dark Mode

Dark mode in v2 is controlled via the theme prop on ConfigProvider. You need to import the following styles in your project:

ts
// main.ts
import 'element-plus/dist/index.css';
import 'element-plus/theme-chalk/dark/css-vars.css';
import 'vue-element-plus-x/styles/index.css';

SSR Compatibility

v2 includes SSR compatibility improvements. If you use SSR deployment, mark the following packages as noExternal in your Vite config:

ts
// vite.config.ts
export default defineConfig({
  ssr: {
    noExternal: [
      'vue-element-plus-x',
      'shiki',
      'shiki-stream',
      'x-markdown-vue'
    ]
  }
});

Full Migration Example

Below is a minimal example showing a complete migration from v1 to v2:

vue
<template>
  <div class="chat-app">
    <BubbleList :list="list" />
    <MentionSender v-model="inputValue" @submit="handleSend" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { BubbleList, MentionSender } from 'vue-element-plus-x';

const list = ref([
  { content: 'Hello! How can I help you?', role: 'assistant' }
]);
const inputValue = ref('');

const handleSend = () => {
  list.value.push({ content: inputValue.value, role: 'user' });
  inputValue.value = '';
};
</script>
vue
<template>
  <ConfigProvider :theme="isDark ? 'dark' : 'light'">
    <div class="chat-app">
      <BubbleList :list="list">
        <template #content="{ item }">
          <MarkdownRenderer :markdown="item.content" :enable-animate="true" />
        </template>
      </BubbleList>
      <XSender
        v-model="inputValue"
        :mention-config="mentionConfig"
        @submit="handleSend"
      />
    </div>
  </ConfigProvider>
</template>

<script setup>
import { ref } from 'vue';
import { BubbleList, XSender, ConfigProvider } from 'vue-element-plus-x';
import { MarkdownRenderer } from 'x-markdown-vue';
import 'x-markdown-vue/style';

const isDark = ref(false);
const mentionConfig = ref({
  /* mention configuration */
});
const list = ref([
  { content: 'Hello! How can I help you?', role: 'assistant' }
]);
const inputValue = ref('');

const handleSend = () => {
  list.value.push({ content: inputValue.value, role: 'user' });
  inputValue.value = '';
};
</script>

FAQ

Q: Styles are broken after upgrading. What should I do?

A: Make sure you have imported the correct style files:

ts
import 'element-plus/dist/index.css';
import 'vue-element-plus-x/styles/index.css';

If you are using dark mode, also import the Element Plus dark styles:

ts
import 'element-plus/theme-chalk/dark/css-vars.css';

Q: What are the differences between XSender and the old EditorSender?

A: XSender has minor API changes — mainly the rename, plus new props like variant for layout and device for device adaptation. For full API details, see the XSender documentation.

Q: How do I set up dark mode in VitePress?

A: See Theme Configuration - VitePress Integration.

Q: Is XRequest still usable?

A: XRequest is currently still available but has been marked as deprecated. It is recommended to migrate to hook-fetch, which provides more comprehensive request management capabilities.

Q: Is the namespace configuration from the old version still valid?

A: The namespace prop is still available and only affects component class name prefixes. However, note that theme CSS variable prefixes are fixed as --elx-* and are not affected by namespace.