Skip to content

Modal Component ​

Pops up a modal dialog, commonly used for message alerts, message confirmation, and completing specific interactions within the current page.

📌 Platform Differences ​

APP(vue)H5WeChat Mini ProgramAlipay Mini Program
✔✔✔✔

⚠️ Notes ​

Notes

  • When closeOnClickOverlay is enabled, clicking the overlay will only trigger the close event; the modal will not close automatically, so you need to handle the closing logic yourself
  • The asyncClose and asyncCancelClose properties have been replaced by the loading and autoClose properties in the current implementation
  • When a custom slot is passed in, the content property will be ignored
  • When using a custom popup component on the current page, it is recommended to explicitly pass :message="false" to hy-modal to declare it as an independent modal, preventing button clicks from being taken over and disabled by the global message callback

🏯 Basic Usage Examples ​

Basic Modal ​

html
<template>
    <view>
        <hy-button text="Open Modal" @click="show = true"></hy-button>
        <hy-modal v-model="show" content="This is a basic modal"></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>
html
<template>
    <view>
        <hy-button text="Open Modal with Title" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="System Notice"
            content="This is an important notice, please read it carefully."
            @confirm="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

With Cancel Button ​

html
<template>
    <view>
        <hy-button text="Open Modal with Cancel Button" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Notice"
            content="Are you sure you want to perform this operation?"
            show-cancel-button
            @confirm="onConfirm"
            @cancel="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);

    const onConfirm = () => {
        uni.showToast({
            title: 'Confirmed',
            icon: 'success',
        });
        show.value = false;
    };
</script>

Swap Button Positions ​

html
<template>
    <view>
        <hy-button text="Open Modal with Swapped Buttons" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Notice"
            content="The cancel button is on the right, the confirm button is on the left"
            show-cancel-button
            button-reverse
            @confirm="show = false"
            @cancel="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

Close on Overlay Click ​

html
<template>
    <view>
        <hy-button text="Close on Overlay Click" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Notice"
            content="Clicking the overlay area closes the modal"
            close-on-click-overlay
            @close="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

Control Modal Width ​

html
<template>
    <view>
        <hy-button text="Custom Width" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Custom Width"
            content="The modal width is 400rpx"
            width="400rpx"
            @confirm="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

Async Close (with Loading State) ​

html
<template>
    <view>
        <hy-button text="Async Close" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Submitting"
            content="Processing your request, please wait..."
            :auto-close="false"
            :loading="loading"
            @confirm="onAsyncConfirm"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
    const loading = ref(false);

    const onAsyncConfirm = () => {
        loading.value = true;
        // Simulate an async operation
        setTimeout(() => {
            uni.showToast({
                title: 'Operation successful',
                icon: 'success',
            });
            loading.value = false;
            show.value = false;
        }, 2000);
    };
</script>
html
<template>
    <view>
        <hy-button text="Open Form Modal" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Fill in Information"
            :auto-close="false"
            :loading="loading"
            @confirm="onSubmit"
            @cancel="show = false"
        >
            <view class="form-content">
                <view class="form-item">
                    <text class="form-label">Name</text>
                    <input v-model="formData.name" class="form-input" placeholder="Please enter a name" />
                </view>
                <view class="form-item">
                    <text class="form-label">Description</text>
                    <textarea
                        v-model="formData.desc"
                        class="form-textarea"
                        placeholder="Please enter a description"
                    />
                </view>
            </view>
        </hy-modal>
    </view>
</template>

<script setup>
    import { ref, reactive } from 'vue';

    const show = ref(false);
    const loading = ref(false);
    const formData = reactive({
        name: '',
        desc: '',
    });

    const onSubmit = () => {
        if (!formData.name) {
            uni.showToast({
                title: 'Please fill in the name',
                icon: 'none',
            });
            return;
        }

        loading.value = true;
        setTimeout(() => {
            uni.showToast({
                title: 'Submitted successfully',
                icon: 'success',
            });
            loading.value = false;
            show.value = false;
        }, 1500);
    };
</script>

<style scoped>
    .form-content {
        padding: 20rpx;
    }

    .form-item {
        margin-bottom: 30rpx;
    }

    .form-label {
        font-size: 28rpx;
        color: #333;
        display: block;
        margin-bottom: 15rpx;
    }

    .form-input {
        width: 100%;
        height: 80rpx;
        border: 1rpx solid #ddd;
        border-radius: 8rpx;
        padding: 0 20rpx;
        font-size: 28rpx;
    }

    .form-textarea {
        width: 100%;
        height: 160rpx;
        border: 1rpx solid #ddd;
        border-radius: 8rpx;
        padding: 20rpx;
        font-size: 28rpx;
    }
</style>

Custom Button Styles ​

html
<template>
    <view>
        <hy-button text="Custom Button Colors" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Custom Buttons"
            content="The confirm button is green, the cancel button is gray"
            show-cancel-button
            confirm-color="#07c160"
            cancel-color="#999"
            @confirm="show = false"
            @cancel="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

Disable Zoom Effect ​

html
<template>
    <view>
        <hy-button text="Disable Zoom Effect" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="No Zoom Effect"
            content="No zoom animation when opening and closing"
            :zoom="false"
            @confirm="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

Custom Border Radius ​

html
<template>
    <view>
        <hy-button text="Custom Border Radius" @click="show = true"></hy-button>
        <hy-modal
            v-model="show"
            title="Custom Border Radius"
            content="The border radius is 30rpx"
            round="30rpx"
            @confirm="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';

    const show = ref(false);
</script>

🎊 Global Message and Custom Modal Coexistence ​

useMessage() injects a global message state into all descendant components, and it needs to be paired with a hy-modal carrying the message property as the driver instance to render popups.

However, when both a global message driver instance and custom modals exist on the same page, the custom hy-modal is also within the injection scope: if it is mistakenly identified as the driver instance, its visibility will follow the global state, and button clicks will be taken over by global callbacks — manifesting as v-model not working and confirm/cancel button clicks having no response.

In this case, explicitly pass :message="false" to the custom modal to declare it as an independent modal, which fully relies on v-model and the confirm/cancel events, free from hijacking by the global injected state.

Complete Example ​

html
<template>
    <view>
        <hy-button text="Global Message Confirmation" @click="onGlobalConfirm"></hy-button>
        <hy-button text="Open Custom Modal" @click="show = true"></hy-button>

        <!-- Global message driver instance: controlled by message.alert / message.confirm -->
        <hy-modal message></hy-modal>

        <!-- Custom modal: explicitly declares :message="false", unaffected by the global injected state -->
        <hy-modal
            v-model="show"
            title="Fill in Information"
            content="This is an independent custom modal"
            show-cancel-button
            @confirm="onConfirm"
            @cancel="show = false"
        ></hy-modal>
    </view>
</template>

<script setup>
    import { ref } from 'vue';
    import { useMessage } from '@hy-app/ui';

    const show = ref(false);

    // Call once in the page (or a common layout component) to provide the global message state to descendant components
    const message = useMessage();

    const onGlobalConfirm = async () => {
        const confirmed = await message.confirm({
            title: 'Delete Confirmation',
            content: 'Are you sure you want to delete this data?'
        });
        if (confirmed) {
            uni.showToast({ title: 'Deleted', icon: 'success' });
        }
    };

    const onConfirm = () => {
        uni.showToast({ title: 'Custom modal confirmed', icon: 'none' });
        show.value = false;
    };
</script>

Tip

  • It is recommended to call useMessage() once in a common layout component and mount <hy-modal /> there, so that all pages using this layout share the same global message instance;
  • Do not bind v-model to the global message driver instance; its visibility is entirely controlled by message.alert / message.confirm;
  • When a custom modal on the same page has unresponsive clicks or a non-working v-model, first check whether :message="false" was omitted.

API ​

Props ​

ParameterDescriptionTypeDefault Value
modelValueWhether to show the modalbooleanfalse
messageWhether to act as the driver instance for the useMessage global message; when true, visibility and callbacks are controlled by message.alert / message.confirm; when false, it is an independent modal that relies on v-model and the confirm/cancel events, free from hijacking by the global injected statebooleanfalse
titleTitle contentstring-
contentModal content; ignored if slot content is passed instring-
confirmTextText of the confirm buttonstringConfirm
cancelTextText of the cancel buttonstringCancel
showConfirmButtonWhether to show the confirm buttonbooleantrue
showCancelButtonWhether to show the cancel buttonbooleanfalse
confirmColorColor of the confirm buttonstring-
cancelColorColor of the cancel buttonstring-
buttonReverseSwap the positions of the confirm and cancel buttonsbooleanfalse
zoomWhether to enable zoom modebooleantrue
roundModal border radius; supports number, px, and rpx unitsstring|number16
autoCloseWhether to automatically close after clicking the confirm buttonbooleantrue
loadingWhether to show the loading statebooleanfalse
closeOnClickOverlayWhether clicking the overlay is allowed to close the Modal (Note: the close event must be handled by yourself; the close callback is only executed on overlay clicks after closeOnClickOverlay is enabled)booleanfalse
negativeTopUpward offset value, applies a negative margin-top to shift the modal upward to avoid overlapping with the keyboard; the default unit for numeric values is pxnumber0
widthModal width; percentages are not supported; the default unit for numeric values is px, and the rpx unit is supportedstring|number550rpx
confirmButtonShapeStyle of the confirm button; when set, the cancel button will not be displayed; optional values: circle, squarestring-
contentTextAlignText alignment; optional values: left, center, rightstringleft
customStyleCustom external styles to be appliedCSSProperties-
customClassCustom external class namestring-

Events ​

Event NameDescriptionCallback Parameters
confirmTriggered when the confirm button is clicked-
cancelTriggered when the cancel button is clicked-
closeTriggered when closing via overlay click; only valid when closeOnClickOverlay is true-

Slots ​

Slot NameDescriptionAccepted Values
defaultPass in custom content, overriding the content property-
confirmButtonPass in a custom button, used for scenarios requiring button-based authorization in WeChat Mini Program popups-

CSS Variables ​

Variable NameDescriptionDefault Value
--hy-modal-max-widthMaximum width of the popup650rpx
--hy-modal-radiusBorder radiusFollows --hy-radius-base (20rpx)
--hy-modal-title-padding-topTitle top padding25px
--hy-modal-content-paddingContent padding20px
--hy-modal-button-group-paddingButton group padding20px
--hy-modal-confirm-button-paddingConfirm button area padding0 25px 15px
--hy-modal-button-heightButton height43px
--hy-modal-button-radiusButton border radiusFollows --hy-radius-semicircle (100px)
--hy-modal-button-font-sizeButton font size16px
--hy-modal-title-font-sizeTitle font sizeFollows --hy-font-size-base (28rpx)
--hy-modal-title-colorTitle colorFollows --hy-text-color
--hy-modal-content-font-sizeContent font sizeFollows --hy-font-size-sm (24rpx)
--hy-modal-content-colorContent colorFollows --hy-text-color--grey
--hy-modal-button-confirm-colorConfirm button color#FFFFFF
--hy-modal-button-cancel-colorCancel button colorFollows --hy-text-color
12:33
battery