---
title: v-morph directive
desc: >-
  Vue directive that morphs between DOM elements or even between the two states
  of the same DOM element.
related:
  - title: Morph Utils
    path: ../quasar-utils/morph-utils.md
---
"Morph" is a Quasar directive that provides the ability to morph DOM elements between two states.

Under the hood, it uses the Quasar [Morph function util](../quasar-utils/morph-utils.md).

## Morph API

### Directive Value

- `value` (object | any, optional)
  Configuration object or trigger value
  Examples:
    - `v-morph:element2:group1="groupModel"`
    - `v-morph="{ name: 'element2', group: 'group1', model: 'element1' }"`
  Object shape:
    - `group` (string, optional)
      Name of the morph group the element belongs to
      Examples: `'dialogGroup'`
    - `name` (string, optional)
      Name of the morph inside the group that the element belongs to
      Examples: `'btn'`
    - `model` (string, optional)
      Current value of the group model; when it becomes the same as the 'name' it triggers the morphing
      Examples: `'btn'`
    - `duration` (number, optional), default `300`
      Duration of the animation (in milliseconds)
    - `delay` (number, optional), default `0`
      Delay for the animation (in milliseconds)
    - `easing` (string, optional), default `'ease-in-out'`
      Timing function for the animation (CSS easing format)
      Examples: `'ease-out'`
    - `fill` (string, optional), default `'none'`
      Fill mode for the animation
      Examples: `'forward'`
    - `classes` (string, optional)
      Class names to be added to the destination element during the animation
      Examples: `'bg-grey-2'`
    - `style` (string | object, optional)
      Styles to be added to the destination element during the animation
      Examples: `'border-radius: 20px'`
    - `resize` (boolean, optional)
      Use resize instead of scaling during animation
    - `useCSS` (boolean, optional)
      Use CSS animations instead of the Animation API
    - `hideFromClone` (boolean, optional)
      Hide the spacer for the initial element during animation; Use it if the initial element is not removed or resizing of the space occupied by the initial element is not desired
    - `keepToClone` (boolean, optional)
      Keep a clone of the final element visible during animation
    - `tween` (boolean, optional)
      Use an opacity tween between the initial and final elements
    - `tweenFromOpacity` (number, optional), default `0.6`
      If using tween it is the initial opacity of the initial element (will be animated to 0) - the initial element is placed on top of the final element
    - `tweenToOpacity` (number, optional), default `0.5`
      If using tween it is the initial opacity of the final element (will be animated to 1)
    - `waitFor` (number | string | Promise<void>, optional), default `0`
      Delay animation start for that number of milliseconds, or until a 'transitionend' event is emitted by the destination element, or until the promise is resolved (if the promise is rejected the morphing will abort, but the 'toggle function' was already called)
      Examples: `300`, `'200'`, `'transitionend'`
    - `onEnd` (Function, optional)
      A function that will be called once the morphing is finished; Not called if morphing is aborted
      Function signature: `(direction?: string, aborted?: boolean) => unknown`
      Examples:
        - `(direction, _aborted) => { if (direction !== 'to') { /* revertLogic() */ } }`

### Directive Argument

- `arg` (string, optional)
  x:x2:y:z, where x is the morph element name, x2 is the morph group, y is the animation duration (in milliseconds) and z is the amount of time to wait (in milliseconds) or the 'transitionend' string
  Examples:
    - `v-morph:name="options"`
    - `v-morph:name:groupName="options"`
    - `v-morph:name:groupName:400="options"`
    - `v-morph:name:groupName:400:100="options"`
    - `v-morph:name:groupName:400:transitionend="options"`

### Directive Modifiers

- `resize` (boolean, optional)
  Use resize instead of scale transform for morph (forceResize option of the morph function)
- `useCSS` (boolean, optional)
  Use CSS animations for morph (forceCssAnimation option of the morph function)
- `hideFromClone` (boolean, optional)
  Hide the spacer for the initial element (hideFromClone option of the morph function)
- `keepToClone` (boolean, optional)
  Keep the final element visible while morphing (keepToClone option of the morph function)
- `tween` (boolean, optional)
  Use opacity tween morphing between initial and final elements (tween option of the morph function)

## Usage

Reading the [Morph function util](../quasar-utils/morph-utils.md) first will be best in your understanding of how this directive works.

This directive morphs one element in a group into another. The morphing is activated by changing the value (model) of the directive to match the name of the morphing element.

> [!WARNING]
> - The "name" and "group" (as directive arg or through the value of the directive) are mandatory.
> - If the value of the directive is in Object form, then "model" is also mandatory.

### Morph between multiple elements in a group

```vue
<template>
  <div
    class="q-pa-md relative-position"
    style="height: 600px; max-height: 80vh"
  >
    <div
      class="absolute-top-left bg-red text-white q-ma-md q-pa-lg"
      style="border-radius: 10px; font-size: 32px"
      v-morph:topleft:boxes:800="morphGroupModel"
    >
      Top left
    </div>

    <div
      class="absolute-top-right bg-blue text-white q-ma-lg q-pa-xl"
      style="border-radius: 20px; font-size: 18px"
      v-morph:topright:boxes:600.tween="morphGroupModel"
    >
      Top right
    </div>

    <div
      class="absolute-bottom-right bg-orange text-white q-ma-lg q-pa-lg"
      style="border-radius: 0"
      v-morph:bottomright:boxes:400="morphGroupModel"
    >
      Bottom right
    </div>

    <div
      class="absolute-bottom-left bg-green text-white q-ma-xl q-pa-md"
      style="border-radius: 40px; font-size: 24px"
      v-morph:bottomleft:boxes:600.resize="morphGroupModel"
    >
      Bottom left
    </div>

    <q-btn
      class="absolute-center"
      color="purple"
      label="Next morph"
      no-caps
      @click="nextMorph"
    />
  </div>
</template>

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

const boxValues = ['topleft', 'topright', 'bottomleft', 'bottomright']
const morphGroupModel = ref('topleft')

function nextMorph() {
  let value = morphGroupModel.value

  // pick random box, other than current one
  while (value === morphGroupModel.value) {
    const i = Math.floor(Math.random() * boxValues.length)
    value = boxValues[i]
  }

  morphGroupModel.value = value
}
</script>
```

### Morph a button into a card

```vue
<template>
  <div
    class="q-pa-md relative-position"
    style="height: 280px; max-height: 80vh"
  >
    <q-btn
      v-morph:btn:mygroup:300.resize="morphGroupModel"
      class="absolute-bottom-left q-ma-md"
      fab
      color="primary"
      size="lg"
      icon="add"
      @click="nextMorph"
    />

    <q-card
      v-morph:card1:mygroup:500.resize="morphGroupModel"
      class="absolute-bottom-left q-ma-md bg-primary text-white"
      style="width: 300px; border-bottom-left-radius: 2em"
    >
      <q-card-section class="text-h6"> New user </q-card-section>

      <q-card-section class="text-subtitle1">
        Please fill the details for a new user.
      </q-card-section>

      <q-card-actions align="right">
        <q-btn flat label="Next" @click="nextMorph" />
      </q-card-actions>
    </q-card>

    <q-card
      v-morph:card2:mygroup:500.tween="morphGroupModel"
      class="absolute-bottom-left q-ma-md bg-primary text-white"
      style="width: 300px; border-bottom-left-radius: 2em"
    >
      <q-card-section class="text-h6"> Finalize registration </q-card-section>

      <q-card-section class="q-py-xl text-center text-subtitle2">
        Thank you for registering.
      </q-card-section>

      <q-card-actions align="right">
        <q-btn flat label="Close" @click="nextMorph" />
      </q-card-actions>
    </q-card>
  </div>
</template>

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

const nextMorphStep = {
  btn: 'card1',
  card1: 'card2',
  card2: 'btn'
}

const morphGroupModel = ref('btn')

function nextMorph() {
  morphGroupModel.value = nextMorphStep[morphGroupModel.value]
}
</script>
```
