Dynamic and Built-In Components

By Syncfusion®, Inc. · Last updated

Switching components with <component :is>, async components with defineAsyncComponent() and their loading, error, delay and timeout options, code splitting, <KeepAlive> with include, exclude, max and activation hooks, <Teleport> for modals and overlays with focus handling, and the experimental <Suspense>.

Components That Change

  • Sometimes which component to show is decided while the app runs.
  • Tabs, wizard steps and widgets on a dashboard all work like this.
  • Vue has one tag for it, <component :is>, and four built-in helpers:

    • defineAsyncComponent loads a component only when needed
    • <KeepAlive> remembers a component's state when it is hidden
    • <Teleport> shows content somewhere else on the page
    • <Suspense> waits for async components to be ready

Simple lesson: :is picks a component at run time. Built-in components control how it loads, lives and where it appears.

A Real-World Example

  • Meera's dish page has three tabs: Details, Reviews and Nutrition.
  • Each tab is its own component.
  • One <component :is> shows the chosen one.
  • What happened: each button changed current. <component :is> showed that component.
  • dish was passed as a prop, like on any component.

What You Will Learn

  1. How <component :is> works.
  2. Why to store components in shallowRef.
  3. How to load a component later with defineAsyncComponent.
  4. Loading, error, delay and timeout options.
  5. How code splitting makes the first load faster.
  6. How <KeepAlive> keeps state.
  7. The onActivated and onDeactivated hooks.
  8. How <Teleport> moves a modal to the end of the page.
  9. What <Suspense> does, and why it is still experimental.

component :is

  • :is takes a component object or the name of a registered component.
  • It can also take an HTML tag name, such as "button" or "a".
  • Props, events and slots work as usual.
  • Switching removes the old component and its state. <KeepAlive> changes that, below.

Store component objects in shallowRef or a plain object, not ref. A deep ref would make the whole component definition reactive, which is wasted work. Vue warns about it.

Simple lesson: <component :is="X" /> renders whatever component X holds.

Async Components

  • defineAsyncComponent(() => import("./Big.vue")) makes a component that loads its code the first time it is shown.
  • Until then, its code is not part of the first download.
  • Code splitting = the build puts that file in its own small chunk.
  • Options make it friendly:

    • loadingComponent: shown while loading
    • delay: wait this long before showing it (default 200 ms), to avoid a flash
    • errorComponent: shown if loading fails
    • timeout: give up after this long
  • What happened: "Loading the chart…" showed, then the chart.
  • The broken widget showed its error component instead of a blank space.

In a real build, import("./MenuChart.vue") downloads a separate JavaScript file the first time. In this lab, every file is already in the preview, so the box adds a fake delay to show the loading state. Routes can be split the same way, as in Navigation Guards and Advanced Routing.

Simple lesson: defineAsyncComponent loads code only when needed. Give it loading and error parts.

KeepAlive

  • Normally, switching away from a component destroys it and its state.
  • Wrap the dynamic component in <KeepAlive> to keep it in memory instead.
  • include and exclude choose which components to keep, by name. A <script setup> component is named after its file (Reviews.vue → Reviews), or set one with defineOptions({ name }).
  • max limits how many are kept. The least recently used is dropped first.
  • Kept components get onActivated and onDeactivated hooks instead of mount and unmount.
  • What happened: your note was still there after switching back.
  • The Console shows deactivated and activated instead of unmount and mount.

include and exclude match the component's name. A <script setup> component is named after its file, such as OrderNote. Use defineOptions({ name }) if you need a different name.

Simple lesson: <KeepAlive> hides components instead of destroying them.

Teleport

  • A modal is written inside a component, but it should appear on top of the whole page.
  • Deep inside other elements, CSS like overflow: hidden or z-index can trap it.
  • <Teleport to="body"> renders its content at the end of <body>, but keeps it part of the component.
  • State, props and events still work normally.
  • :disabled="true" keeps it in place, for example on small screens.

Animation: a modal written inside OrderCard is moved by Teleport to the end of the body element, outside the card, while its logic stays in the component

OrderCard (overflow: hidden)

end of

Confirm order? modal

Written in OrderCard. Shown at the end of body. Logic stays in OrderCard.

Teleport moves where the HTML appears, not where the code lives.

  • What happened: the modal escaped the card's overflow: hidden box.
  • Focus moved into the modal, and back to the button after closing. Esc also closes it.

A good modal: has role="dialog" and aria-modal="true", moves focus inside when it opens, closes on Esc, and returns focus to the button that opened it.

Simple lesson: <Teleport to="body"> puts modals and overlays at the end of the page.

Suspense (Experimental)

  • An async component here means a <script setup> that uses await at the top level.
  • <Suspense> waits until every async child below it is ready.
  • Until then, it shows its #fallback slot.
  • Events: pending, resolve and fallback.
  • It is still experimental: its API may change. Many apps show their own loading state instead.

Simple lesson: <Suspense> shows a fallback until async children are ready. Use it with care.

TL;DR

  • <component :is> = renders the component (or tag) held in a variable.
  • shallowRef = the right way to store a component object.
  • defineAsyncComponent = loads a component's code on first use.
  • Async options = loadingComponent, delay, errorComponent, timeout.
  • Code splitting = lazy parts become separate, smaller files.
  • <KeepAlive> = keeps hidden components and their state. include, exclude, max.
  • onActivated / onDeactivated = hooks for kept components.
  • <Teleport to="body"> = renders content elsewhere, keeps the logic here.
  • <Suspense> = experimental. Shows #fallback until async children are ready.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Component in a deep refAny ref worksUse shallowRefA warning and wasted work
No loading or error componentLazy parts load instantlySet loadingComponent and errorComponentBlank space, or nothing on failure
Expecting state to survive a switchVue keeps itWrap in <KeepAlive>Typed text is lost
include with the wrong nameIt matches the tagIt matches the component nameNothing is kept
Modal inside overflow: hiddenz-index fixes it<Teleport to="body">The modal is cut off or hidden
Modal without focus handlingClicking is enoughFocus in, Esc to close, focus backKeyboard users get lost
Suspense everywhereIt is a stable core featureUse it sparingly, it is experimentalCode may break on upgrades

Check Yourself

Exercises

What to Remember

  • <component :is> switches components. Store them in shallowRef.
  • defineAsyncComponent loads code on demand. Add loading and error parts.
  • <KeepAlive> keeps state. Kept components get onActivated and onDeactivated.
  • <Teleport to="body"> frees modals from parent CSS. Handle focus and Esc.
  • <Suspense> is experimental. Prefer your own loading states for now.

Next: Transitions and Animations