---
title: Popup Edit
desc: >-
  The QPopupEdit Vue component can be used to edit a value 'in place', like for
  example on a cell in QTable.
related:
  - title: Input
    path: input.md
  - title: QMenu
    path: menu.md
---
The QPopupEdit component can be used to edit a value “in place”, like for example a cell in QTable. By default, a cell is displayed as a String, then if you are using QPopupEdit and a user clicks/taps on the table cell, a popup will open where the user will be able to edit the value using a textfield.

This component injects a [QMenu](menu.md) into its parent DOM element and enables the behavior described above, so **it can be used anywhere**, not only in QTable. QMenu's props are passed through via this component (except for `model-value`, which is QPopupEdit's own edited model), along with QMenu's `escape-key` event.

## QPopupEdit API

### Props

- `model-value` (any, required, syncable)
  Model of the component; Either use this property (along with a listener for 'update:model-value' event) OR use v-model directive
  Examples: `v-model="myValue"`
- `title` (string, optional)
  Optional title (unless 'title' slot is used)
  Examples: `'Calories'`
- `buttons` (boolean, optional)
  Show Set and Cancel buttons
- `label-set` (string, optional)
  Override Set button label
  Examples: `'OK'`
- `label-cancel` (string, optional)
  Override Cancel button label
  Examples: `'Cancel'`
- `auto-save` (boolean, optional)
  Automatically save the model (if changed) when user clicks/taps outside of the popup; It does not apply to ESC key
- `color` (string, optional), default `'primary'`
  Color name for component from the Quasar Color Palette
  Examples: `'primary'`, `'teal'`, `'teal-10'`
- `validate` (Function, optional), default `() => true`
  Validates model then triggers 'save' and closes Popup; Returns a Boolean ('true' means valid, 'false' means abort); Syntax: validate(value); For best performance, reference it from your scope and do not define it inline
  Function signature: `(value?: any) => boolean`
  Examples: `value => value !== 0`
- `disable` (boolean, optional)
  Put component in disabled mode
- `hover` (boolean, optional) *(added v2.26)*
  Also opens the menu when the pointer hovers its target and closes it when the pointer leaves both the target and the menu; Click/tap and keyboard interactions keep toggling as usual (touch devices fall back to them), so activating the target closes a hover-shown menu, unless it is still animating into view (which keeps a single move-and-click gesture from closing what it just opened); A hover-opened menu does not switch focus on itself and ignores 'touch-position'; Mutually exclusive with 'context-menu', which takes precedence
- `hover-delay` (number, optional), default `0` *(added v2.26)*
  Delay (in milliseconds) between the pointer entering the target and the menu showing up; Requires the 'hover' prop
- `hover-hide-delay` (number, optional), default `150` *(added v2.26)*
  Grace period (in milliseconds) in which the pointer can re-enter the target or the menu before it gets closed; Requires the 'hover' prop
- `transition-show` (string, optional), default `'fade'`
  One of Quasar's embedded transitions
  Examples: `'fade'`, `'slide-down'`
- `transition-hide` (string, optional), default `'fade'`
  One of Quasar's embedded transitions
  Examples: `'fade'`, `'slide-down'`
- `transition-duration` (string | number, optional), default `300`
  Transition duration (in milliseconds, without unit)
- `target` (boolean | string | Element, optional), default `true`
  Configure a target element to trigger component toggle; 'true' means it enables the parent DOM element, 'false' means it disables attaching events to any DOM elements; By using a String (CSS selector) or a DOM element it attaches the events to the specified DOM element (if it exists)
  Examples: `false`, `.my-parent`, `#target-id`, `$refs.target`
- `no-parent-event` (boolean, optional)
  Skips attaching events to the target DOM element (that trigger the element to get shown)
- `context-menu` (boolean, optional)
  Allows the component to behave like a context menu, which opens with a right mouse click (or a long press on touch-capable devices)
- `dark` (boolean, optional), default `null`
  Notify the component that the background is a dark color
- `fit` (boolean, optional)
  Allows the menu to match at least the full width of its target
- `cover` (boolean, optional), default `true`
  Allows the menu to cover its target. When used, the 'self' and 'fit' props are no longer effective
- `anchor` (string, optional)
  Two values setting the starting position or anchor point of the menu relative to its target
  Accepts: `'top left'`, `'top middle'`, `'top right'`, `'top start'`, `'top end'`, `'center left'`, `'center middle'`, `'center right'`, `'center start'`, `'center end'`, `'bottom left'`, `'bottom middle'`, `'bottom right'`, `'bottom start'`, `'bottom end'`
- `self` (string, optional)
  Two values setting the menu's own position relative to its target
  Accepts: `'top left'`, `'top middle'`, `'top right'`, `'top start'`, `'top end'`, `'center left'`, `'center middle'`, `'center right'`, `'center start'`, `'center end'`, `'bottom left'`, `'bottom middle'`, `'bottom right'`, `'bottom start'`, `'bottom end'`
- `offset` (any[], optional)
  An array of two numbers (in pixels) which expands the anchor element's bounding box outward horizontally and vertically; the menu is then positioned against the expanded box, so the visible effect depends on the 'anchor'/'self' points in use
  Examples:
    - `[8, 8]`
    - `[5, 10]`
- `touch-position` (boolean, optional)
  Allows for the target position to be set by the mouse position, when the target of the menu is either clicked or touched
- `persistent` (boolean, optional)
  Allows the menu to not be dismissed by a click/tap outside of the menu or by hitting the ESC key; Also, an app route change won't dismiss it
- `no-esc-dismiss` (boolean, optional) *(added v2.18)*
  User cannot dismiss the popup by hitting ESC key; No need to set it if 'persistent' prop is also set
- `no-route-dismiss` (boolean, optional)
  Changing route app won't dismiss the popup; No need to set it if 'persistent' prop is also set
- `auto-close` (boolean, optional)
  Allows any click/tap in the menu to close it; Useful instead of attaching events to each menu item that should close the menu on click/tap
- `separate-close-popup` (boolean, optional)
  Separate from parent menu, marking it as a separate closing point for v-close-popup (without this, chained menus close all together)
- `square` (boolean, optional)
  Forces content to have squared borders
- `no-refocus` (boolean, optional)
  (Accessibility) When Menu gets hidden, do not refocus on the DOM element that previously had focus
- `no-focus` (boolean, optional)
  (Accessibility) When Menu gets shown, do not switch focus on it
- `max-height` (string, optional), default `null`
  The maximum height of the menu; Size in CSS units, including unit name
  Examples: `'16px'`, `'2rem'`
- `max-width` (string, optional), default `null`
  The maximum width of the menu; Size in CSS units, including unit name
  Examples: `'16px'`, `'2rem'`

### Methods

- `set(): void`
  Trigger a model update; Validates model (and emits 'save' event if it's the case) then closes Popup
- `cancel(): void`
  Triggers a model reset to its initial value ('cancel' event is emitted) then closes Popup
- `show(evt?: Event): void`
  Triggers component to show
  Params:
    - `evt` (Event, optional)
      JS event object
- `hide(evt?: Event): void`
  Triggers component to hide
  Params:
    - `evt` (Event, optional)
      JS event object
- `updatePosition(): void`
  There are some custom scenarios for which Quasar cannot automatically reposition the component without significant performance drawbacks so the optimal solution is for you to call this method when you need it

### Events

- `@update:model-value`
  Emitted when Popup gets cancelled in order to reset model to its initial value; Is also used by v-model
  Params:
    - `value` (any, required)
      New model value
- `@before-show`
  Emitted right before Popup gets shown
- `@show`
  Emitted right after Popup gets shown
- `@before-hide`
  Emitted right before Popup gets dismissed
- `@hide`
  Emitted right after Popup gets dismissed
- `@save`
  Emitted when value has been successfully validated and it should be saved
  Params:
    - `value` (any, optional)
      Validated value to be saved
    - `initialValue` (any, optional)
      Initial value, before changes
- `@cancel`
  Emitted when user cancelled the change (hit ESC key or clicking outside of Popup or hit 'Cancel' button)
  Params:
    - `value` (any, optional)
      Edited value
    - `initialValue` (any, optional)
      Initial value, before changes
- `@escape-key`
  Emitted when ESC key is pressed; Does not get emitted if Menu is 'persistent' or it has 'no-esc-dismiss' set

### Scoped Slots

- `#default`
  Used for injecting the form component; Do NOT destructure it
  Scope:
    - `initialValue` (any, optional)
      Initial value
      Examples: `0.241`, `'Text'`
    - `value` (any, optional)
      Current value
      Examples: `0.241`, `'Text'`
    - `validate` (Function, optional)
      Function that checks if the value is valid
      Function signature: `(value: any) => boolean`
    - `set` (Function, optional)
      Function that sets the value and closes the popup
    - `cancel` (Function, optional)
      Function that cancels the editing and reverts the value to the initialValue
    - `updatePosition` (Function, optional)
      There are some custom scenarios for which Quasar cannot automatically reposition the component without significant performance drawbacks so the optimal solution is for you to call this method when you need it

## Usage

> [!WARNING]
> If used on a QTable, QPopupEdit won't work with cell scoped slots.

### Standalone

```vue
<template>
  <div class="q-pa-md">
    <div class="cursor-pointer">
      {{ label }}
      <q-popup-edit v-model="label" auto-save v-slot="scope">
        <q-input
          v-model="scope.value"
          dense
          autofocus
          counter
          @keyup.enter="scope.set"
        />
      </q-popup-edit>
    </div>
  </div>
</template>

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

const label = ref('Click me')
</script>
```

### With QTable

Click on the cells to see the popup editor. The column "Name" demonstrates the `title` prop. The column "Calories" displays a numeric value usage. The column "Fat" also demonstrates the `disable` prop. If you look at the source code, you'll see the cell for "fat" is using QPopupEdit, yet when clicking on the cell, the popup doesn't show.

### Edit first columns

```vue
<template>
  <div class="q-pa-md">
    <q-table
      :rows="rows"
      :columns="columns"
      title="QDataTable with QPopupEdit"
      :rows-per-page-options="[]"
      row-key="name"
    >
      <template v-slot:body="props">
        <q-tr :props="props">
          <q-td key="desc" :props="props">
            {{ props.row.name }}
            <q-popup-edit
              v-model="props.row.name"
              title="Edit the Name"
              auto-save
              v-slot="scope"
            >
              <q-input
                v-model="scope.value"
                dense
                autofocus
                counter
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="calories" :props="props">
            {{ props.row.calories }}
            <q-popup-edit
              v-model.number="props.row.calories"
              auto-save
              v-slot="scope"
            >
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="fat" :props="props">
            {{ props.row.fat }}
            <q-popup-edit
              disable
              v-model="props.row.fat"
              auto-save
              v-slot="scope"
            >
              <div class="text-italic text-primary q-mb-xs">
                My Custom Title
              </div>

              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="carbs" :props="props">
            {{ props.row.carbs }}
          </q-td>
          <q-td key="protein" :props="props">
            {{ props.row.protein }}
          </q-td>
          <q-td key="sodium" :props="props">
            {{ props.row.sodium }}
          </q-td>
          <q-td key="calcium" :props="props">
            {{ props.row.calcium }}
            <q-popup-edit v-model="props.row.calcium" v-slot="scope">
              <div class="text-italic text-primary"> My Custom Title </div>
              <q-input
                v-model="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="iron" :props="props">
            {{ props.row.iron }}
          </q-td>
        </q-tr>
      </template>
    </q-table>
  </div>
</template>

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

const columns = [
  // ...
]

const rows = ref([
  // ...
])
</script>
```

### Customizing

```vue
<template>
  <div class="q-pa-md">
    <div class="q-gutter-md">
      <div class="cursor-pointer" style="width: 100px">
        {{ label }}
        <q-popup-edit
          v-model="label"
          class="bg-accent text-white"
          v-slot="scope"
        >
          <q-input
            dark
            color="white"
            v-model="scope.value"
            dense
            autofocus
            counter
            @keyup.enter="scope.set"
          >
            <template v-slot:append>
              <q-icon name="edit" />
            </template>
          </q-input>
        </q-popup-edit>
      </div>

      <div class="cursor-pointer" style="width: 100px">
        {{ label2 }}
        <q-popup-edit
          v-model="label2"
          :cover="false"
          :offset="[0, 10]"
          v-slot="scope"
        >
          <q-input
            color="accent"
            v-model="scope.value"
            dense
            autofocus
            counter
            @keyup.enter="scope.set"
          >
            <template v-slot:prepend>
              <q-icon name="record_voice_over" color="accent" />
            </template>
          </q-input>
        </q-popup-edit>
      </div>
    </div>
  </div>
</template>

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

const label = ref('Click me')
const label2 = ref('Also click me')
</script>
```

### Persistent and with buttons

You can also add two buttons with the `buttons` prop, "Cancel" and "Set" (the default labels). These buttons help to control the user's input. Along with the `buttons` prop, you also have the `persistent` prop, which denies the user from closing the popup with the escape key or clicking/ tapping outside of the popup. Lastly, you can control the labels of the two buttons with the `label-set` and `label-cancel` props, as seen in the "Protein" column. Notice "Save" is replacing "Set" and "Close" is replacing "Cancel".

> The `persistent` prop is demonstrated in the "carbs" column.

### Persistent edit, and with buttons

```vue
<template>
  <div class="q-pa-md">
    <q-table
      :rows="rows"
      :columns="columns"
      title="QDataTable with QPopupEdit"
      :rows-per-page-options="[]"
      row-key="name"
    >
      <template v-slot:body="props">
        <q-tr :props="props">
          <q-td key="desc" :props="props">
            {{ props.row.name }}
            <q-popup-edit v-model="props.row.name" buttons v-slot="scope">
              <q-input
                v-model="scope.value"
                dense
                autofocus
                counter
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="calories" :props="props">
            {{ props.row.calories }}
            <q-popup-edit
              v-model.number="props.row.calories"
              buttons
              v-slot="scope"
            >
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="fat" :props="props">
            <div class="text-pre-wrap">{{ props.row.fat }}</div>
            <q-popup-edit v-model.number="props.row.fat" buttons v-slot="scope">
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="carbs" :props="props">
            {{ props.row.carbs }}
            <q-popup-edit
              v-model.number="props.row.carbs"
              buttons
              persistent
              v-slot="scope"
            >
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="protein" :props="props">
            {{ props.row.protein }}
            <q-popup-edit
              v-model.number="props.row.protein"
              buttons
              label-set="Save"
              label-cancel="Close"
              v-slot="scope"
            >
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="sodium" :props="props">
            {{ props.row.sodium }}
            <q-popup-edit
              v-model.number="props.row.sodium"
              buttons
              v-slot="scope"
            >
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="calcium" :props="props">
            {{ props.row.calcium }}
            <q-popup-edit v-model="props.row.calcium" buttons v-slot="scope">
              <q-input
                v-model="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="iron" :props="props">
            {{ props.row.iron }}
            <q-popup-edit v-model="props.row.iron" buttons v-slot="scope">
              <q-input
                v-model="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
        </q-tr>
      </template>
    </q-table>
  </div>
</template>

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

const columns = [
  // ...
]

const rows = ref([
  // ...
])
</script>
```

### The default slot

The default slot's parameters are:

```js
{
  initialValue, value, validate, set, cancel, updatePosition
}
```

> [!WARNING]
> Do not destructure the slot's parameters as it will generate linting errors when using the `value` prop directly with `v-model`.

### Default slot parameters

```vue
<template>
  <div class="q-pa-md">
    <div class="cursor-pointer">
      {{ nickname }}
      <q-popup-edit
        v-model="nickname"
        :validate="val => val.length > 5"
        v-slot="scope"
      >
        <q-input
          autofocus
          dense
          v-model="scope.value"
          :model-value="scope.value"
          hint="Your nickname"
          :rules="[val => scope.validate(val) || 'More than 5 chars required']"
        >
          <template v-slot:after>
            <q-btn
              flat
              dense
              color="negative"
              icon="cancel"
              @click.stop.prevent="scope.cancel"
            />

            <q-btn
              flat
              dense
              color="positive"
              icon="check_circle"
              @click.stop.prevent="scope.set"
              :disable="
                !scope.validate(scope.value) ||
                scope.initialValue === scope.value
              "
            />
          </template>
        </q-input>
      </q-popup-edit>
    </div>
  </div>
</template>

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

const nickname = ref('Click me')
</script>
```

### Textarea / QEditor

Since QPopupEdit wraps QInput, you can basically use any type of QInput. For instance, you can also use a text area as shown below in the "Comments" column.

> [!TIP]
> When using a multi-line control (textarea, QEditor) for input, you'll need to also use `@keyup.enter.stop` on the component in order to stop the enter key from closing the popup. You'll also need to add buttons for controlling the popup too.

### QInput textarea

```vue
<template>
  <div class="q-pa-md">
    <q-table
      :rows="rows"
      :columns="columns"
      title="QDataTable with QPopupEdit"
      :rows-per-page-options="[]"
      row-key="name"
      wrap-cells
    >
      <template v-slot:body="props">
        <q-tr :props="props">
          <q-td key="desc" :props="props">
            {{ props.row.name }}
          </q-td>

          <q-td key="comment" :props="props">
            <div>{{ props.row.comment }}</div>
            <q-popup-edit buttons v-model="props.row.comment" v-slot="scope">
              <q-input
                type="textarea"
                v-model="scope.value"
                autofocus
                counter
                @keyup.enter.stop
              />
            </q-popup-edit>
          </q-td>

          <q-td key="calories" :props="props">
            {{ props.row.calories }}
          </q-td>

          <q-td key="fat" :props="props">
            <div>{{ props.row.fat }}</div>
          </q-td>
        </q-tr>
      </template>
    </q-table>
  </div>
</template>

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

const columns = [
  {
    name: 'desc',
    style: 'min-width: 160px; width: 160px',
    align: 'left',
    label: 'Dessert',
    field: 'name'
  },
  {
    name: 'comment',
    style: 'min-width: 200px; width: 200px',
    align: 'left',
    label: 'Comment (editable)',
    field: 'comment'
  },
  { name: 'calories', align: 'center', label: 'Calories', field: 'calories' },
  { name: 'fat', label: 'Fat (g)', field: 'fat' }
]

const rows = ref([
  // ...
])
</script>
```

### QEditor

```vue
<template>
  <div class="q-pa-md">
    <q-table
      :rows="rows"
      :columns="columns"
      title="QDataTable with QPopupEdit"
      :rows-per-page-options="[]"
      row-key="name"
      wrap-cells
    >
      <template v-slot:body="props">
        <q-tr :props="props">
          <q-td key="desc" :props="props">
            {{ props.row.name }}
            <q-popup-edit v-model="props.row.name" v-slot="scope">
              <q-input
                v-model="scope.value"
                dense
                autofocus
                counter
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>

          <q-td key="comment" :props="props">
            <div v-html="props.row.comment"></div>
            <q-popup-edit buttons v-model="props.row.comment" v-slot="scope">
              <q-editor
                v-model="scope.value"
                min-height="5rem"
                autofocus
                @keyup.enter.stop
              />
            </q-popup-edit>
          </q-td>

          <q-td key="calories" :props="props">
            {{ props.row.calories }}
            <q-popup-edit v-model.number="props.row.calories" v-slot="scope">
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>

          <q-td key="fat" :props="props">
            <div class="text-pre-wrap">{{ props.row.fat }}</div>
            <q-popup-edit v-model.number="props.row.fat" v-slot="scope">
              <q-input
                type="number"
                v-model.number="scope.value"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
        </q-tr>
      </template>
    </q-table>
  </div>
</template>

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

const columns = [
  {
    name: 'desc',
    style: 'min-width: 160px; width: 160px',
    align: 'left',
    label: 'Dessert',
    field: 'name'
  },
  {
    name: 'comment',
    style: 'min-width: 200px; width: 200px',
    align: 'left',
    label: 'Comment (editable)',
    field: 'comment'
  },
  { name: 'calories', align: 'center', label: 'Calories', field: 'calories' },
  { name: 'fat', label: 'Fat (g)', field: 'fat' }
]

const rows = ref([
  // ...
])
</script>
```

### Validation

QPopupEdit also allows for simple validation of the input. To use it, you give it a callback function in the form of an arrow function and it should return a Boolean. `(value) => Boolean`. This is **demonstrated in the "Calories" column** below.

> [!TIP]
> **Tip 1**
> Notice we are using the `hide` event to also revalidate the input. If we don't, QInput's error prop will 'hang' in an invalid state.

> [!TIP]
> **Tip 2**
> With this example, we are using QInput's external error handling. We could also use QInput's validation prop and emit the value to QPopupEdit's validation prop. The same concept can be implemented, when using [Regle](https://reglejs.dev/) external validation library too. In other words, the value given to QPopupEdit's validate function can come from anywhere.

### Edit with validation

```vue
<template>
  <div class="q-pa-md">
    <q-table
      :rows="rows"
      :columns="columns"
      title="QDataTable with QPopupEdit"
      :rows-per-page-options="[]"
      row-key="name"
    >
      <template v-slot:body="props">
        <q-tr :props="props">
          <q-td key="desc" :props="props">
            {{ props.row.name }}
          </q-td>
          <q-td key="calories" :props="props">
            {{ props.row.calories }}
            <q-popup-edit
              v-model.number="props.row.calories"
              buttons
              label-set="Save"
              label-cancel="Close"
              :validate="caloriesRangeValidation"
              @hide="caloriesRangeValidation"
              v-slot="scope"
            >
              <q-input
                type="number"
                v-model.number="scope.value"
                hint="Enter a number between 4 and 7"
                :error="errorCalories"
                :error-message="errorMessageCalories"
                dense
                autofocus
                @keyup.enter="scope.set"
              />
            </q-popup-edit>
          </q-td>
          <q-td key="fat" :props="props">
            <div class="text-pre-wrap">{{ props.row.fat }}</div>
          </q-td>
          <q-td key="carbs" :props="props">
            {{ props.row.carbs }}
          </q-td>
          <q-td key="protein" :props="props">
            {{ props.row.protein }}
          </q-td>
        </q-tr>
      </template>
    </q-table>
  </div>
</template>

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

const columns = [
  { name: 'desc', align: 'left', label: 'Dessert', field: 'name' },
  {
    name: 'calories',
    align: 'center',
    label: 'Calories (editable)',
    field: 'calories'
  },
  { name: 'fat', label: 'Fat', field: 'fat' },
  { name: 'carbs', label: 'Carbs', field: 'carbs' },
  { name: 'protein', label: 'Protein', field: 'protein' }
]

const rows = ref([
  // ...
])

const errorCalories = ref(false)
const errorMessageCalories = ref('')

function caloriesRangeValidation(val) {
  if (val < 4 || val > 7) {
    errorCalories.value = true
    errorMessageCalories.value = 'The value must be between 4 and 7!'
    return false
  }
  errorCalories.value = false
  errorMessageCalories.value = ''
  return true
}
</script>
```

## Accessibility *(v2.25+)*

QPopupEdit is built on [QMenu](menu.md), so the popup itself claims no ARIA role — see [QMenu's Accessibility section](menu.md#accessibility) for the underlying keyboard and focus behavior. <kbd>Escape</kbd> cancels the edit and returns focus to the element the popup covers, and closing the popup any other way without going through validation never silently commits: depending on the `auto-save` prop, either a value that passes validation is saved or `cancel` is emitted. When using the `buttons` prop, "Set" and "Cancel" render as real buttons whose default labels come localized from the [Quasar Language Pack](../options/quasar-language-packs.md) (override them with `label-set` / `label-cancel`).

A few responsibilities remain yours: put `autofocus` on your input so keyboard focus lands in the editor as soon as the popup opens; wire <kbd>Enter</kbd>-to-save yourself with `@keyup.enter="scope.set"` (as the examples above do); and note that the `title` prop renders purely visual text — it is not wired up as the popup's accessible name.
