Sender Input Box 💭
📌 Warning
Added in version 1.1.6
- Variant
Up-Down Structure
- Custom bottom
#footer
slot - Custom directive
Popover
and callback event for triggering the directive
🐵 This warm tip was last updated: 2025-04-16
Introduction
Sender
is an input box component for chat scenarios. It features rich interactive functions and customization options. It supports voice input, clearing input content, multiple submission methods, and allows users to customize the header, prefix, and action list. The component also provides focus control, submission callbacks, and more, to meet diverse input scenarios.
Code Examples
Basic Usage
This is a Sender
input component, the simplest usage example.
<template>
<Sender />
</template>
2
3
Placeholder
You can set the input placeholder through the placeholder
attribute.
<template>
<Sender placeholder="💌 Welcome to Element-Plus-X ~" />
</template>
2
3
Two-way Binding (Unbound, value will not change)
You can bind the component's value
property through v-model
.
📌 Warning
- When submitting, content is required for submission to proceed.
- When content is empty, the submit button will be disabled, and using component instance submission will fail.
💌 Info
- Through the
v-model
attribute, you can automatically bind the input value. No need to assign data tov-model
. - Through the
@submit
event, you can trigger the input submission event, which returns avalue
parameter where you can handle the submitted data. - Through the
@cancel
event, you can trigger theloading
button click event. Here you can abort the submission operation.
You can also call through the component ref instance object
senderRef.value.submit()
Trigger submissionsenderRef.value.cancel()
Trigger cancelsenderRef.value.clear()
Reset input value
<script setup lang="ts">
const senderRef = ref();
const timeValue = ref<NodeJS.Timeout | null>(null);
const senderValue = ref('');
const senderLoading = ref(false);
const submitBtnDisabled = ref(true);
function handleSubmit(value: string) {
ElMessage.info(`Sending`);
senderLoading.value = true;
timeValue.value = setTimeout(() => {
// You can view the print result in the console
console.log('submit-> value:', value);
console.log('submit-> senderValue', senderValue.value);
senderLoading.value = false;
ElMessage.success(`Sent successfully`);
}, 3500);
}
function handleCancel() {
senderLoading.value = false;
if (timeValue.value)
clearTimeout(timeValue.value);
timeValue.value = null;
ElMessage.info(`Cancel sending`);
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex">
<el-button
type="primary"
style="width: fit-content"
@click="senderRef.clear()"
>
Clear using component instance
</el-button>
<el-button
type="primary"
style="width: fit-content"
:disabled="!senderValue"
@click="senderRef.submit()"
>
Submit using component instance
</el-button>
<el-button
type="primary"
style="width: fit-content"
@click="senderRef.cancel()"
>
Cancel using component instance
</el-button>
</div>
<Sender
ref="senderRef"
v-model="senderValue"
:submit-btn-disabled="submitBtnDisabled"
:loading="senderLoading"
clearable
@submit="handleSubmit"
@cancel="handleCancel"
/>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Submit Button Disabled State
You can customize whether to disable the send button through submit-btn-disabled
. When disabled, the component instance's submit
method will fail.
📌 Warning
The built-in send button is bound to v-model
, so when the v-model
bound value is empty, the send button will be in disabled state.
However, there is such a scenario. The user uploaded a file but didn't input content, at this time, the send button is still in disabled state.
So, for decoupling the disable logic, the component provides the submit-btn-disabled
attribute for autonomous control of the send button's disabled state.
When customizing #action-list
, this attribute also takes effect for submit events.
<script setup lang="ts">
const senderRef = ref();
const timeValue = ref<NodeJS.Timeout | null>(null);
const senderValue = ref('');
const senderLoading = ref(false);
const submitBtnDisabled = ref(true);
function handleSubmit(value: string) {
ElMessage.info(`Sending`);
senderLoading.value = true;
timeValue.value = setTimeout(() => {
// You can view the print result in the console
console.log('submit-> value:', value);
console.log('submit-> senderValue', senderValue.value);
senderLoading.value = false;
ElMessage.success(`Sent successfully`);
}, 3500);
}
function handleCancel() {
senderLoading.value = false;
if (timeValue.value)
clearTimeout(timeValue.value);
timeValue.value = null;
ElMessage.info(`Cancel sending`);
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<span>This is the built-in disable logic:</span>
<Sender
ref="senderRef"
v-model="senderValue"
:loading="senderLoading"
clearable
@submit="handleSubmit"
@cancel="handleCancel"
/>
<span>Custom disable logic:</span>
<Sender
ref="senderRef"
v-model="senderValue"
:submit-btn-disabled="submitBtnDisabled"
:loading="senderLoading"
clearable
@submit="handleSubmit"
@cancel="handleCancel"
/>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Custom Max and Min Rows
You can set the minimum and maximum display rows for the input through autosize
. autosize
is an object with default value { minRows: 1, maxRows: 6 }
. When exceeding the maximum rows, the input will automatically show a scrollbar.
<script setup lang="ts">
const longerValue = `💌 Welcome to Element-Plus-X ~`.repeat(30);
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Sender :auto-size="{ minRows: 2, maxRows: 5 }" />
<Sender v-model="longerValue" />
</div>
</template>
2
3
4
5
6
7
8
9
10
Various States of the Input Component
You can implement component states through simple properties
💌 Info
- Through the
loading
property, you can control whether the input is loading. - Through the
readOnly
property, you can control whether the input is editable. - Through the
disabled
property, you can control whether the input is disabled. - Through the
clearable
property, you can control whether the input shows a delete button for clearing. - Through the
inputWidth
property, you can control the input width. Default is100%
.
<script setup lang="ts">
const senderReadOnlyValue = ref(`Read-only: 💌 Welcome to Element-Plus-X ~`);
const senderClearableValue = ref(`Clearable: 💌 Welcome to Element-Plus-X ~`);
function handleSubmit(value: string) {
console.log(value);
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Sender loading placeholder="Loading..." @submit="handleSubmit" />
<Sender v-model="senderReadOnlyValue" read-only @submit="handleSubmit" />
<Sender
value="Disabled: 💌 Welcome to Element-Plus-X ~"
disabled
@submit="handleSubmit"
/>
<Sender v-model="senderClearableValue" clearable @submit="handleSubmit" />
<Sender
style="width: fit-content"
value="Input max width: 💌 Welcome to Element-Plus-X ~"
input-width="150px"
@submit="handleSubmit"
/>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Submission Methods
Control line breaks and submit mode with submitType
. Default is 'enter'
, i.e., press Enter to submit, 'shift + Enter'
for a new line.
💌 Info
submitType='enter'
sets Enter to submit,'shift + Enter'
for a new line.submitType='shiftEnter'
sets'shift + Enter'
to submit, Enter for a new line.submitType='cmdOrCtrlEnter'
sets'cmd + Enter'
or'ctrl + Enter'
to submit, Enter for a new line.submitType='altEnter'
sets'alt + Enter'
to submit, Enter for a new line.
<script setup lang="ts">
import type { SenderProps } from 'vue-element-plus-x/types/Sender';
const activeName = ref<SenderProps['submitType']>('enter');
const senderValue = ref('');
const senderLoading = ref(false);
function handleSubmit(value: string) {
ElMessage.info(`Sending...`);
senderLoading.value = true;
setTimeout(() => {
// You can check the console for the output
console.log('submit-> value:', value);
console.log('submit-> senderValue', senderValue.value);
senderLoading.value = false;
ElMessage.success(`Sent successfully`);
}, 2000);
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<el-radio-group v-model="activeName">
<el-radio-button value="enter">
enter
</el-radio-button>
<el-radio-button value="shiftEnter">
shiftEnter
</el-radio-button>
<el-radio-button value="cmdOrCtrlEnter">
cmdOrCtrlEnter
</el-radio-button>
<el-radio-button value="altEnter">
altEnter
</el-radio-button>
</el-radio-group>
<Sender
v-model="senderValue"
:submit-type="activeName"
:loading="senderLoading"
@submit="handleSubmit"
/>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Speech Recognition
📌 Warning
Built-in browser speech recognition API. You can use the useRecord
hook for easier integration and control of built-in speech recognition.
Built-in voice recognition
functionality, enable it through the allowSpeech
property. It calls the browser's native voice recognition API, and when used in Google Chrome
, it needs to be in a 🪄magic environment
to work properly.
💌 Info
If you don't want to use the built-in voice recognition
functionality, you can listen to the recording state through the @recording-change
event and implement voice recognition functionality yourself.
You can also call through the component ref instance object
senderRef.value.startRecognition()
Trigger start recordingsenderRef.value.stopRecognition()
Trigger stop recording
<script setup lang="ts">
const senderRef = ref();
const senderValue = ref('');
function onRecordingChange(recording: boolean) {
if (recording) {
ElMessage.success('Start recording');
}
else {
ElMessage.success('Stop recording');
}
}
function onsubmit() {
ElMessage.success('Sent successfully');
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<span>Built-in voice recognition:</span>
<Sender v-model="senderValue" allow-speech @submit="onsubmit" />
<span>Custom voice recognition:</span>
<div style="display: flex">
<el-button
type="primary"
style="width: fit-content"
@click="senderRef.startRecognition()"
>
Start recording using component instance
</el-button>
<el-button
type="primary"
style="width: fit-content"
@click="senderRef.stopRecognition()"
>
Stop recording using component instance
</el-button>
</div>
<Sender
ref="senderRef"
v-model="senderValue"
allow-speech
@recording-change="onRecordingChange"
/>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Variant - Vertical Style
Set the input variant through the variant
property. Default 'default' | Up-down structure 'updown'
This property changes the left-right structure input into an up-down structure input. The top is the input box, and the bottom is the built-in prefix and action list bar
<script setup lang="ts">
import { ElementPlus, Paperclip, Promotion } from '@element-plus/icons-vue';
const senderValue = ref('');
const isSelect = ref(false);
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 20px">
<Sender v-model="senderValue" variant="updown" />
<Sender v-model="senderValue" variant="updown" clearable />
<Sender v-model="senderValue" variant="updown" clearable allow-speech />
<Sender
v-model="senderValue"
variant="updown"
:auto-size="{ minRows: 2, maxRows: 5 }"
clearable
allow-speech
placeholder="💌 Here you can customize the prefix and action-list after variant"
>
<template #prefix>
<div
style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap"
>
<el-button round plain color="#626aef">
<el-icon><Paperclip /></el-icon>
</el-button>
<div
:class="{ isSelect }"
style="
display: flex;
align-items: center;
gap: 4px;
padding: 2px 12px;
border: 1px solid silver;
border-radius: 15px;
cursor: pointer;
font-size: 12px;
"
@click="isSelect = !isSelect"
>
<el-icon><ElementPlus /></el-icon>
<span>Deep Thinking</span>
</div>
Left is custom prefix, right is custom action list
</div>
</template>
<template #action-list>
<div style="display: flex; align-items: center; gap: 8px">
<el-button round color="#626aef">
<el-icon><Promotion /></el-icon>
</el-button>
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="scss">
.isSelect {
color: #626aef;
border: 1px solid #626aef !important;
border-radius: 15px;
padding: 3px 12px;
font-weight: 700;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Custom Action List
📌 Warning
Before version 1.0.81
, when using custom slots, the built-in action buttons would be sacrificed. In version 1.0.81
, we launched streaming request hooks that allow users to better control streaming requests, thus better defining the #action-list
slot. For details, please check our project template's main request library, comparable to Axios hook-fetch.
This friendly reminder update time: 2025-07-05
Use the #action-list
slot to customize the input action list content.
💌 Info
When you use the #action-list
slot, the built-in input action buttons will be hidden. You can combine it with component instance methods
to implement richer operations.
<script setup lang="ts">
import {
Delete,
Loading,
Operation,
Position,
Promotion,
Right,
Setting
} from '@element-plus/icons-vue';
const senderRef = ref();
const senderValue = ref('');
const loading = ref(false);
function handleSubmit() {
console.log('submit', senderValue.value);
senderRef.value.submit();
loading.value = true;
}
function handleCancel() {
console.log('cancel');
loading.value = false;
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Sender>
<!-- Custom action list -->
<template #action-list>
<div class="action-list-self-wrap">
<el-button type="danger" circle>
<el-icon><Delete /></el-icon>
</el-button>
<el-button type="primary" circle style="rotate: -45deg">
<el-icon><Position /></el-icon>
</el-button>
</div>
</template>
</Sender>
<Sender>
<!-- Custom action list -->
<template #action-list>
<div class="action-list-self-wrap">
<el-button type="primary" plain circle color="#626aef">
<el-icon><Operation /></el-icon>
</el-button>
<el-button type="primary" circle color="#626aef">
<el-icon><Right /></el-icon>
</el-button>
</div>
</template>
</Sender>
<Sender>
<!-- Custom action list -->
<template #action-list>
<div class="action-list-self-wrap">
<el-button plain circle color="#eebe77">
<el-icon><Setting /></el-icon>
</el-button>
<el-button type="primary" plain circle>
<el-icon><Promotion /></el-icon>
</el-button>
</div>
</template>
</Sender>
<Sender ref="senderRef" v-model="senderValue" :loading="loading">
<!-- Custom action list -->
<template #action-list>
<div class="action-list-self-wrap">
<el-button
v-if="loading"
type="primary"
plain
circle
@click="handleCancel"
>
<el-icon class="is-loaidng">
<Loading />
</el-icon>
</el-button>
<el-button v-else plain circle @click="handleSubmit">
<el-icon><Position /></el-icon>
</el-button>
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="less">
.action-list-self-wrap {
display: flex;
align-items: center;
& > span {
width: 120px;
font-weight: 600;
color: var(--el-color-primary);
}
}
.is-loaidng {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
Custom Prefix
Use the #prefix
slot to customize the input prefix content.
<script setup lang="ts">
import { CircleClose, Link } from '@element-plus/icons-vue';
const senderRef = ref();
const senderValue = ref('');
const showHeaderFlog = ref(false);
onMounted(() => {
showHeaderFlog.value = true;
senderRef.value.openHeader();
});
function openCloseHeader() {
if (!showHeaderFlog.value) {
senderRef.value.openHeader();
}
else {
senderRef.value.closeHeader();
}
showHeaderFlog.value = !showHeaderFlog.value;
}
function closeHeader() {
showHeaderFlog.value = false;
senderRef.value.closeHeader();
}
</script>
<template>
<div
style="
display: flex;
flex-direction: column;
gap: 12px;
height: 230px;
justify-content: flex-end;
"
>
<Sender ref="senderRef" v-model="senderValue">
<template #header>
<div class="header-self-wrap">
<div class="header-self-title">
<div class="header-left">
💯 Welcome to Element Plus X
</div>
<div class="header-right">
<el-button @click.stop="closeHeader">
<el-icon><CircleClose /></el-icon>
<span>Close Header</span>
</el-button>
</div>
</div>
<div class="header-self-content">
🦜 Custom Header Content
</div>
</div>
</template>
<!-- Custom prefix -->
<template #prefix>
<div class="prefix-self-wrap">
<el-button dark>
<el-icon><Link /></el-icon>
<span>Custom Prefix</span>
</el-button>
<el-button color="#626aef" :dark="true" @click="openCloseHeader">
Open/Close Header
</el-button>
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="less">
.header-self-wrap {
display: flex;
flex-direction: column;
padding: 16px;
height: 200px;
.header-self-title {
width: 100%;
display: flex;
height: 30px;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
}
.header-self-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #626aef;
font-weight: 600;
}
}
.prefix-self-wrap {
display: flex;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
Custom Header
Use the #header
slot to customize the input header content.
💌 Info
Control header container expand/collapse through component instance
senderRef.value.openHeader()
Open header containersenderRef.value.closeHeader()
Close header container
<script setup lang="ts">
import { CircleClose } from '@element-plus/icons-vue';
const senderRef = ref();
const senderValue = ref('');
const showHeaderFlog = ref(false);
onMounted(() => {
showHeaderFlog.value = true;
senderRef.value.openHeader();
});
function openCloseHeader() {
if (!showHeaderFlog.value) {
senderRef.value.openHeader();
}
else {
senderRef.value.closeHeader();
}
showHeaderFlog.value = !showHeaderFlog.value;
}
function closeHeader() {
showHeaderFlog.value = false;
senderRef.value.closeHeader();
}
</script>
<template>
<div
style="
display: flex;
flex-direction: column;
gap: 12px;
height: 300px;
justify-content: space-between;
"
>
<el-button style="width: fit-content" @click="openCloseHeader">
{{ showHeaderFlog ? 'Close Header' : 'Open Header' }}
</el-button>
<Sender ref="senderRef" v-model="senderValue">
<template #header>
<div class="header-self-wrap">
<div class="header-self-title">
<div class="header-left">
💯 Welcome to Element Plus X
</div>
<div class="header-right">
<el-button @click.stop="closeHeader">
<el-icon><CircleClose /></el-icon>
<span>Close Header</span>
</el-button>
</div>
</div>
<div class="header-self-content">
🦜 Custom Header Content
</div>
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="less">
.header-self-wrap {
display: flex;
flex-direction: column;
padding: 16px;
height: 200px;
.header-self-title {
width: 100%;
display: flex;
height: 30px;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
}
.header-self-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #626aef;
font-weight: 600;
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Custom Footer
Set input bottom content through the #footer
slot
💌 Info
If you want to set the #footer
slot and don't want the built-in layout of the updown variant, you can add the showUpdown
property to hide the built-in layout of the updown variant
<script setup lang="ts">
import { ElementPlus, Paperclip, Promotion } from '@element-plus/icons-vue';
const senderValue = ref('');
const isSelect = ref(false);
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 20px">
<Sender
v-model="senderValue"
:auto-size="{ minRows: 1, maxRows: 5 }"
clearable
allow-speech
placeholder="💌 Welcome to Element-Plus-X"
>
<template #prefix>
<div
style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap"
>
<el-button round plain color="#626aef">
<el-icon><Paperclip /></el-icon>
</el-button>
</div>
</template>
<template #action-list>
<div style="display: flex; align-items: center; gap: 8px">
<el-button round color="#626aef">
<el-icon><Promotion /></el-icon>
</el-button>
</div>
</template>
<!-- Custom footer slot -->
<template #footer>
<div
style="
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
"
>
Default variant custom footer
</div>
</template>
</Sender>
<Sender
v-model="senderValue"
variant="updown"
:auto-size="{ minRows: 2, maxRows: 5 }"
clearable
allow-speech
placeholder="💌 Here you can customize the prefix and action-list after variant"
>
<template #prefix>
<div
style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap"
>
<el-button round plain color="#626aef">
<el-icon><Paperclip /></el-icon>
</el-button>
<div
:class="{ isSelect }"
style="
display: flex;
align-items: center;
gap: 4px;
padding: 2px 12px;
border: 1px solid silver;
border-radius: 15px;
cursor: pointer;
font-size: 12px;
"
@click="isSelect = !isSelect"
>
<el-icon><ElementPlus /></el-icon>
<span>Deep Thinking</span>
</div>
</div>
</template>
<template #action-list>
<div style="display: flex; align-items: center; gap: 8px">
<el-button round color="#626aef">
<el-icon><Promotion /></el-icon>
</el-button>
</div>
</template>
<!-- Custom footer slot -->
<template #footer>
<div
style="
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
"
>
updown variant custom footer
</div>
</template>
</Sender>
<Sender
v-model="senderValue"
variant="updown"
:auto-size="{ minRows: 2, maxRows: 5 }"
clearable
allow-speech
placeholder="💌 Hide updown variant built-in layout by setting showUpdown to false"
:show-updown="false"
>
<template #prefix>
<div
style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap"
>
<el-button round plain color="#626aef">
<el-icon><Paperclip /></el-icon>
</el-button>
<div
:class="{ isSelect }"
style="
display: flex;
align-items: center;
gap: 4px;
padding: 2px 12px;
border: 1px solid silver;
border-radius: 15px;
cursor: pointer;
font-size: 12px;
"
@click="isSelect = !isSelect"
>
<el-icon><ElementPlus /></el-icon>
<span>Deep Thinking</span>
</div>
</div>
</template>
<template #action-list>
<div style="display: flex; align-items: center; gap: 8px">
<el-button round color="#626aef">
<el-icon><Promotion /></el-icon>
</el-button>
</div>
</template>
<!-- Custom footer slot -->
<template #footer>
<div
style="
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
text-align: center;
"
>
showUpdown property hides updown variant built-in layout style +
#footer bottom slot combination, completely letting you control the
bottom content
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="scss">
.isSelect {
color: #626aef;
border: 1px solid #626aef !important;
border-radius: 15px;
padding: 3px 12px;
font-weight: 700;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
Custom Input Box Style
Pass through input styles conveniently through input-style
<script setup lang="ts">
import { ElementPlus, Paperclip, Promotion } from '@element-plus/icons-vue';
const senderValue = ref('This is custom input style');
const isSelect = ref(false);
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 20px">
<Sender
v-model="senderValue"
variant="updown"
:input-style="{
backgroundColor: 'rgb(243 244 246)',
color: '#626aef',
fontSize: '24px',
fontWeight: 700
}"
style="background: rgb(243 244 246); border-radius: 8px"
/>
<Sender
v-model="senderValue"
variant="updown"
:input-style="{
backgroundColor: 'transparent',
color: '#F0F2F5',
fontSize: '24px',
fontWeight: 700
}"
style="
background-image: linear-gradient(to left, #434343 0%, black 100%);
border-radius: 8px;
"
/>
<Sender
v-model="senderValue"
:input-style="{
backgroundColor: 'transparent',
color: '#FF5454',
fontSize: '20px',
fontWeight: 700
}"
style="
background-image: linear-gradient(
to top,
#fdcbf1 0%,
#fdcbf1 1%,
#e6dee9 100%
);
border-radius: 8px;
"
/>
<Sender
v-model="senderValue"
variant="updown"
:input-style="{
backgroundColor: 'transparent',
color: '#303133',
fontSize: '16px',
fontWeight: 700
}"
style="
background-image: linear-gradient(
to top,
#d5d4d0 0%,
#d5d4d0 1%,
#eeeeec 31%,
#efeeec 75%,
#e9e9e7 100%
);
border-radius: 8px;
"
>
<template #prefix>
<div
style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap"
>
<el-button round plain color="#626aef">
<el-icon><Paperclip /></el-icon>
</el-button>
<div
:class="{ isSelect }"
style="
display: flex;
align-items: center;
gap: 4px;
padding: 2px 12px;
border: 1px solid black;
border-radius: 15px;
cursor: pointer;
font-size: 12px;
color: black;
"
@click="isSelect = !isSelect"
>
<el-icon><ElementPlus /></el-icon>
<span>Deep Thinking</span>
</div>
</div>
</template>
<template #action-list>
<div style="display: flex; align-items: center; gap: 8px">
<el-button round color="#626aef">
<el-icon><Promotion /></el-icon>
</el-button>
</div>
</template>
</Sender>
</div>
</template>
<style scoped lang="scss">
.isSelect {
color: #626aef !important;
border: 1px solid #626aef !important;
border-radius: 15px;
padding: 3px 12px;
font-weight: 700;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
Trigger Directive
Input box with built-in directive popover for convenient directive operations
💌 Info
- Set directive trigger characters through the
triggerStrings
property, type is a character array ['/', '@'] - Control whether the directive popover is visible through v-model binding
triggerPopoverVisible
property
v-model:trigger-popover-visible="triggerVisible"
- Set directive popover width through
triggerPopoverWidth
property, default'fit-content'
- Set directive popover distance from left through
triggerPopoverLeft
property, default'0px'
- Set distance between directive popover and input through
triggerPopoverOffset
property, default8
- Set directive popover position through
triggerPopoverPlacement
property, same as el-popover's placement property, default'top-start'
Values 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'
@trigger
Set callback method for directive popover show/hide changes
💡 Tip
@trigger
When you want to do something when a directive is triggered but don't want the built-in popover style, you can not use v-model:trigger-popover-visible="triggerVisible", so the built-in popover won't appear
<script setup lang="ts">
import type { TriggerEvent } from 'vue-element-plus-x/types/Sender';
const senderValue = ref('');
const senderValue1 = ref('');
const triggerVisible = ref(false);
const dialogVisible = ref(false);
function onTrigger(event: TriggerEvent) {
console.log('onTrigger', event);
}
function onTrigger1(event: TriggerEvent) {
console.log('onTrigger1', event);
dialogVisible.value = event.isOpen;
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 20px">
<Sender
v-model="senderValue"
v-model:trigger-popover-visible="triggerVisible"
placeholder="Input / and @ to trigger directive popover"
clearable
:trigger-strings="['/', '@']"
trigger-popover-width="400px"
trigger-popover-left="0px"
:trigger-popover-offset="10"
trigger-popover-placement="top-start"
@trigger="onTrigger"
/>
<Sender
v-model="senderValue1"
placeholder="Input XXX and QQ to trigger directive popover. Here we don't use v-model:trigger-popover-visible binding, but can still trigger @trigger event. Please check the console for trigger events"
clearable
:trigger-strings="['XXX', 'QQ']"
trigger-popover-width="400px"
trigger-popover-left="0px"
:trigger-popover-offset="30"
trigger-popover-placement="top-start"
@trigger="onTrigger1"
/>
<el-dialog
v-model="dialogVisible"
title="💖 Welcome to Element-Plus-X"
width="500"
>
<span>Trigger event has been executed, can be opening popover, opening
drawer, any event you need ~</span>
</el-dialog>
</div>
</template>
<style scoped lang="scss"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Custom Directive Popover
💡 Tip
Custom popover content. If you only need to match the beginning of a certain character, you can use this component.
📌 Warning
💌 Friendly Reminder: Since V1.3.1, the component ref can access the popover open state property popoverVisible
and the built-in input instance inputInstance
.
This means:
- You can perform some logic based on whether the popover is open.
- The popover can support richer custom events.
This reminder date: 2025-07-21
<script setup lang="ts">
import type { TriggerEvent } from 'vue-element-plus-x/types/Sender';
import { ElMessage } from 'element-plus';
import { Sender } from 'vue-element-plus-x';
const senderValue = ref('');
const triggerVisible = ref(false);
const senderRef = ref<InstanceType<typeof Sender>>();
onMounted(() => {
window.addEventListener('keydown', handleWindowKeydown);
senderRef.value?.inputInstance.addEventListener(
'keydown',
handleInputKeydown
);
});
onUnmounted(() => {
window.removeEventListener('keydown', handleWindowKeydown);
senderRef.value?.inputInstance.removeEventListener(
'keydown',
handleInputKeydown
);
});
function onTrigger(event: TriggerEvent) {
ElMessage.success('Command triggered');
console.log('onTrigger', event);
}
function handleWindowKeydown(e: KeyboardEvent) {
switch (e.key) {
case 'w':
ElMessage.success(`w pressed, input is not affected`);
console.log('w pressed');
break;
case 'a':
ElMessage.success(`a pressed, input is not affected`);
console.log('a pressed');
break;
case 's':
ElMessage.success(`s pressed, input is not affected`);
console.log('s pressed');
break;
case 'd':
ElMessage.success(`d pressed, input is not affected`);
console.log('d pressed');
break;
}
}
// When the popover is visible, prevent some key events in the input to avoid conflicts with global custom keyboard events for the mention popover
function handleInputKeydown(e: KeyboardEvent) {
if (['w', 'a', 's', 'd'].includes(e.key)) {
e.preventDefault();
}
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 20px">
<Sender
ref="senderRef"
v-model="senderValue"
v-model:trigger-popover-visible="triggerVisible"
placeholder="Type / or @ to trigger the command popover"
clearable
:trigger-strings="['/', '@']"
trigger-popover-width="400px"
trigger-popover-left="0px"
:trigger-popover-offset="10"
trigger-popover-placement="top-start"
@trigger="onTrigger"
>
<!-- Custom mention popover -->
<template #trigger-popover="{ triggerString }">
The current trigger character is: {{ `${triggerString}` }}
This is my custom popover. Here you can customize the popover content,
including custom key controls for the popover. Try controlling the
directions with the w/a/s/d keys.
</template>
</Sender>
</div>
</template>
<style scoped lang="scss"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
Input Focus Control
Control focus through ref option.
💌 Info
Control through component instance
senderRef.value.focus('all')
Focus on entire text (default)senderRef.value.focus('start')
Focus on text beginningsenderRef.value.focus('end')
Focus on text endsenderRef.value.blur()
Remove focus
<script setup lang="ts">
const senderRef = ref();
const senderValue = ref('🐳 Welcome to Element Plus X');
function blur() {
senderRef.value.blur();
}
function focus(type = 'all') {
senderRef.value.focus(type);
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex">
<el-button dark type="success" plain @click="focus('start')">
Text Beginning
</el-button>
<el-button dark type="success" plain @click="focus('end')">
Text End
</el-button>
<el-button dark type="success" plain @click="focus('all')">
Entire Text
</el-button>
<el-button dark type="success" plain @click="blur">
Remove Focus
</el-button>
</div>
<Sender ref="senderRef" v-model="senderValue" />
</div>
</template>
<style scoped lang="less">
.header-self-wrap {
display: flex;
flex-direction: column;
padding: 16px;
height: 200px;
.header-self-title {
width: 100%;
display: flex;
height: 30px;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
}
.header-self-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #626aef;
font-weight: 600;
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Props
Name | Type | Required | Default | Description |
---|---|---|---|---|
v-model | String | No | '' | Bound value of the input box, use v-model for two-way binding. |
placeholder | String | No | '' | Placeholder text for the input box. |
auto-size | Object | No | { minRows:1, maxRows:6 } | Set the minimum and maximum number of visible rows for the input box. |
read-only | Boolean | No | false | Whether the input box is read-only. |
disabled | Boolean | No | false | Whether the input box is disabled. |
submitBtnDisabled | Boolean | undefined | No | undefined | Disable the built-in send button. (Note the usage scenario) |
loading | Boolean | No | false | Whether to show the loading state. When true , the input box will display a loading animation. |
clearable | Boolean | No | false | Whether the input box can be cleared. Shows the default clear button. |
allowSpeech | Boolean | No | false | Whether to allow voice input. Shows the built-in speech recognition button by default, using the browser's built-in speech recognition API. |
submitType | String | No | 'enter' | Submission method. Supports 'shiftEnter' (submit with Shift + Enter ), 'cmdOrCtrlEnter' (submit with Command + Enter or Ctrl + Enter ), 'altEnter' (submit with Alt + Enter ). |
headerAnimationTimer | Number | No | 300 | Custom header display duration in ms. |
inputWidth | String | No | '100%' | Width of the input box. |
variant | String | No | 'default' | Variant type of the input box. Supports 'default' , 'updown' . |
showUpdown | Boolean | No | true | Whether to show the built-in style when the variant is updown . |
inputStyle | Object | No | {} | Style of the input box. |
triggerStrings | string[] | No | [] | String array for trigger directives. |
triggerPopoverVisible | Boolean | No | false | Whether the popover for the trigger directive is visible. Control with v-model:triggerPopoverVisible . |
triggerPopoverWidth | String | No | 'fit-content' | Width of the popover for the trigger directive. Supports percentage and other CSS units. |
triggerPopoverLeft | String | No | '0px' | Left margin of the popover for the trigger directive. Supports percentage and other CSS units. |
triggerPopoverOffset | Number | No | 8 | Offset of the popover for the trigger directive. Must be a number, unit is px. |
triggerPopoverPlacement | String | No | 'top-start' | Placement of the popover for the trigger directive. Values: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end' |
Events
Event Name | Description | Callback Parameter |
---|---|---|
submit | Triggered when the built-in submit button is clicked. | None |
cancel | Triggered when the built-in loading button is clicked. | None |
recordingChange | Triggered when the built-in speech recognition state changes. | None |
trigger | Triggered when the directive popover changes. | interface TriggerEvent{oldValue: string; newValue: string; isOpen: boolean; } |
Ref Instance Methods
Name | Type | Description |
---|---|---|
openHeader | Function | Open the custom header of the input box. |
closeHeader | Function | Close the custom header of the input box. |
clear | Function | Clear the content of the input box. |
blur | Function | Remove focus from the input box. |
focus | Function | Focus the input box. By default, focus('all') selects all text, focus('start') focuses at the start, focus('end') focuses at the end. |
submit | Function | Submit the input content. |
cancel | Function | Cancel the loading state. |
startRecognition | Function | Start speech recognition. |
stopRecognition | Function | Stop speech recognition. |
popoverVisible | Boolean | Popover visibility for the trigger directive. |
inputInstance | Object | Input box instance. |
Slots
Slot Name | Parameter | Type | Description |
---|---|---|---|
#header | - | Slot | For customizing the header content. |
#prefix | - | Slot | For customizing the prefix content. |
#action-list | - | Slot | For customizing the action list. |
#footer | - | Slot | For customizing the footer content. |
Features
- Focus Control: Supports setting focus to the start, end, or selecting all text, and can also remove focus.
- Custom Content: Provides slots for header, prefix, and action list, allowing users to customize these parts.
- Submission Function: Supports submitting input with
Shift + Enter
, and allows custom actions after submission. - Loading State: Can display a loading state to simulate the submission process.
- Voice Input: Supports voice input for more convenient input.
- Clear Function: The input box can be cleared for easy re-entry.