跪拜 Guibai
← Back to the summary

vue-layerx Turns Any Vue Dialog Into a One-Line Imperative Call

vue-layerx 1.0.1 is officially released.

Docs: https://xuyimingwork.github.io/vue-layerx/
Repo: https://github.com/xuyimingwork/vue-layerx

When a business page has many pop-ups, the template often ends up looking like this:

If you've stepped into these pitfalls, this can serve as a "practical" solution introduction.

See the Result First: Open a Dialog with Three Lines of Code

const useDialog = createLayer(ElDialog)
const dialog = useDialog(HelloWorld)
dialog.open()

Breaking it down, it's three things:

  1. Adapter: Use the existing Dialog / Drawer / Popup in your project to generate useDialog
  2. Bind: Pass in a regular business content component
  3. Open: Call imperatively, no need to pile up layer templates and visible on the page anymore

vue-layerx's stance is clear:

It doesn't touch component libraries; it only provides an imperative API, separating the "layer shell" from the "business content" for orchestration.

Core Model: Container × Content

A single layer has only two roles:

Responsibility Example
Container Shell for visibility, mask, title bar, etc. ElDialog, ElDrawer, project's BaseDialog
Content Regular business component, reusable on pages UserForm, detail panel

Corresponding API pipeline:

This separation brings new governance space:

From Adapter to Open: Minimum Viable Path

1. Adapt an Existing Container

// dialog.ts
import { createLayer } from 'vue-layerx'
import { ElDialog } from 'element-plus'

export const useDialog = createLayer(ElDialog, {
  props: {
    // Configure some default properties
    width: '480px',
    destroyOnClose: true,
    appendToBody: true,
  }
})

Element Plus, Ant Design Vue, Vant, or a project's internal BaseDialog all work—as long as the component controls visibility via v-model (or a similar visibility prop).

2. Content is Just a Regular Component

<!-- HelloWorld.vue -->
<template>
  <p>Hello World</p>
</template>

3. Open Imperatively When Used

<script setup lang="ts">
import HelloWorld from './HelloWorld.vue'
import { useDialog } from './dialog'

const dialog = useDialog(HelloWorld)
</script>

<template>
  <button @click="dialog.open({ props: {
    // You can pass parameters to the content here
    message: `Hello`
  } })">Open Layer</button>
</template>

No more Dialog boilerplate in the page template; no need to maintain visible variables.

Slots Continue Using Templates

The problem with imperative components is that after handing the component to the tool for rendering, there is no component in the template, and no place to insert slots.

Most solutions make you pass JSX nodes, but this increases project complexity.

In vue-layerx, you can use LayerTemplate:

<script setup lang="ts">
import { LayerTemplate } from 'vue-layerx'
import HelloWorld from './HelloWorld.vue'
import { useDialog } from './dialog'

const dialog = useDialog(HelloWorld)
</script>

<template>
  <button @click="dialog.open()">Open Layer</button>
  <LayerTemplate :to="dialog" name="footer" container>
    I will appear in the dialog's footer slot
  </LayerTemplate>
</template>

No mandatory JSX, lower learning cost for the team.

Content Components Can Also Pass Templates

It's a bit troublesome to have the caller pass footer content every time, and the business logic isn't self-contained.

vue-layerx also provides defineLayer, allowing content components to pass configuration or slot templates to the container.

<!-- HelloWorld.vue -->
<script setup lang="ts">
import { defineLayer, LayerTemplate } from 'vue-layerx'

const layer = defineLayer({
  // Configure the dialog container from the content component
  props: { width: '380px' }
})
</script>
<template>
  <p>Hello World</p>
  <LayerTemplate :to="layer" name="footer">
    I will appear in the dialog's footer slot
  </LayerTemplate>
</template>

Q: What's the difference between writing it this way and directly writing HelloWorldDialog.vue?

A: The difference is that HelloWorld.vue and ElDialog.vue are separated. HelloWorld.vue can be used as a regular component anywhere on a page, and can be arbitrarily combined with other container components like ElDrawer.vue.

For existing HelloWorldDialog.vue files in a project, vue-layerx provides a progressive adoption plan! See: Container and Content Not Yet Separated

Enterprise-Oriented: Configurations Can Stack, and Can Be "Nailed Down" at the End

In real projects, the same configuration appears in multiple places:

vue-layerx allows them to stack, with the convention that later ones have higher priority.

But sometimes a project doesn't want a "default value" but rather "this must be the case after merging"—for example, a site-wide ban on closing by clicking the mask.

In this case, use adapter: after configurations from all places are merged, modify the result one more time.

import type { LayerAdapter } from 'vue-layerx'
import { createLayer } from 'vue-layerx'
import { ElDialog } from 'element-plus'

const enforceMaskNotClosable: LayerAdapter = (config) => ({
  ...config,
  container: {
    ...config.container,
    props: {
      ...config.container?.props,
      closeOnClickModal: false,
    },
  },
})

export const useDialog = createLayer(ElDialog, {
  adapter: enforceMaskNotClosable,
})

Yes, vue-layerx provides AOP capabilities

Other Capabilities

Who It's For / Who It's Not For

More suitable for:

Not necessary for now:

Quality Assurance

I am also the author of vue-asyncx

vue-asyncx solves async boilerplate code.

This time, vue-layerx solves layer boilerplate—also compositional, minimally invasive, and reusing your existing code as much as possible.

Try It Now

pnpm add vue-layerx

The documentation site has many clickable examples, feel free to experience them