---
url: "https://vueda.dev/v3/reference/components/action-workflow.md"
title: "Action & Workflow Views"
---

<script setup>
import { demoResponse } from "../../.vitepress/theme/fixtures/demoApi.js";
import { customerScenario } from "../../.vitepress/theme/fixtures/showcaseRecords.js";

// One scenario per live demo. Route registration is global and first-match-wins, so demos
// that need different responses for the same model take different app labels.
const archiveScenario = customerScenario({ app: "showcaseaction", actions: [{ name: "archive" }] });
const duplicateScenario = customerScenario({ app: "showcaseduplicate", actions: [{ name: "duplicate" }] });
const activateScenario = customerScenario({
    app: "showcaseactivate",
    actions: [{ name: "activate", method: "PATCH" }],
});
const deactivateScenario = customerScenario({
    app: "showcasedeactivate",
    actions: [{ name: "deactivate", method: "PATCH" }],
});
const historyScenario = customerScenario({ app: "showcasehistory" });
const emptyHistoryScenario = customerScenario({ app: "showcasehistoryempty", history: [] });
// Rejects the pre-flight the way a server does when some records cannot take the action:
// a 400 keyed by primary key. The confirmed request would succeed.
const billScenario = customerScenario({
    app: "showcasebill",
    actions: [
        {
            name: "bill_now",
            handler: ({ headers }) =>
                headers?.get("Dry-Run")?.toLowerCase() === "true"
                    ? demoResponse(400, {
                          11: ["No payment method on file."],
                          23: ["Subscription is paused; resume it before billing."],
                      })
                    : demoResponse(200, { detail: "Billed 3 customers." }),
        },
    ],
});
</script>

# Action & Workflow Views

This page shows the action confirmation views and the history view in the default theme: [ViewAction](/v3/reference/api/vue/ViewAction.md), [ViewActivate](/v3/reference/api/vue/ViewActivate.md), [ViewDeactivate](/v3/reference/api/vue/ViewDeactivate.md), [ViewExecuteTransition](/v3/reference/api/vue/ViewExecuteTransition.md), and [ViewHistoryList](/v3/reference/api/vue/ViewHistoryList.md). [CRUD Views](/v3/reference/components/views-crud.md#viewdestroy) shows `ViewDestroy`, which uses the same confirmation layout. [Components](/v3/reference/components/index.md) describes the rules every component page shares.

[Action Contract and Availability](/v3/core-concepts/action-contract-and-availability.md) describes which actions a view offers and which actions honor a [Dry Run](/v3/reference/glossary.md#dry-run). [Design Transition UX and Redirects](/v3/guides/transition-ux-and-redirects.md) describes what an action form does on pre-flight, submit, and cancel, and where it goes afterward.

## Action card

Every action view renders its confirmation in [ModelActionForm](/v3/reference/api/vue/ModelActionForm.md), with the [`page`](/v3/reference/api/vue/ModelActionForm.md#prop-page) prop set. The page follows the layout of `ViewRead` and `ViewUpdate`:

- The page title holds the title and a "Go Back" button.
- The sticky action bar from [ActionForm](/v3/reference/api/vue/ActionForm.md) holds the confirm button. The button names the action in sentence case, such as "Archive" or "Submit for approval". The page has no separate cancel button, because "Go Back" leaves the page.
- The page body, in the page gutter, holds the banner, the selected-records panel, the prompt "Are you sure you want to ...", and any fields the action collects.

The banner is an icon tile, a title, and a one-line description. The title combines the action name and the model's verbose name. The description defaults to "Review the selected records before continuing." The banner is a required part of every action view. It states the action and the records it targets before the user reaches the confirm button, and its tone signals the action's risk.

`ModelActionForm` without `page` renders the same parts as a card for use outside a routed page, such as in a dialog. The card has a bordered edge and a tinted banner, and [ActionForm](/v3/reference/api/vue/ActionForm.md)'s actions strip below the body holds the confirm button and a "Cancel, go back" button.

The [`tone`](/v3/reference/api/vue/ModelActionForm.md#prop-tone) prop sets that risk level. The form carries the value as `data-tone`, and the banner icon tile changes with it. On a card, the card edge and the banner fill change too.

| `tone`           | Meaning                                     | Default for                           | Banner icon      |
| ---------------- | ------------------------------------------- | ------------------------------------- | ---------------- |
| `info` (default) | A neutral confirmation                      | `ViewAction`, `ViewExecuteTransition` | Information      |
| `success`        | Activate or restore                         | `ViewActivate`                        | Check mark       |
| `warning`        | An irreversible change that deletes nothing | `ViewDeactivate`                      | Warning triangle |
| `danger`         | A permanent deletion                        | [ViewDestroy](/v3/reference/api/vue/ViewDestroy.md)      | Warning triangle |

At `danger`, the selected-records panel and its chips also take the destructive tint, and the default confirm button takes the destructive button tone. At every other tone, the confirm button keeps the primary tone.

The selected-records panel is headed "Selected" plus the model's verbose name, with an "N of N selected" count. Each record is a chip that shows the record's [Formatted Name](/v3/reference/glossary.md#formatted-name) as a [LinkModelView](/v3/reference/api/vue/LinkModelView.md) link. A view can pass the records it fetched as [`fetchState`](/v3/reference/api/vue/ModelActionForm.md#prop-fetchState). Without it, `ModelActionForm` looks the records up by primary key for display only: the action still targets the primary keys it was given. A chip shows the primary key instead when the record has no formatted name, or when the lookup fails.

Each chip is a [FormField](/v3/reference/api/vue/FormField.md) named for its record's primary key. [Server Feedback](/v3/reference/glossary.md#server-feedback) keyed by primary key therefore appears beside the matching chip. [ActionForm.validation](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-validation) lists, above the card, any error that no rendered field shows.

The card's states:

- **Pre-flight.** When the view opens, the card sends the action as a dry run. Its server feedback appears on the form without a toast.
- **Running.** While a request runs, both buttons are disabled and show a spinner.
- **Server feedback only.** The confirm button stays enabled while only server feedback remains, because the server checks the records again on confirm.
- **Success.** A success toast appears, and the buttons stay disabled until the next page replaces the view.
- **Warnings.** A [Warning Confirmation](/v3/reference/glossary.md#warning-confirmation) opens [FormConfirmDialog](/v3/reference/api/vue/FormConfirmDialog.md). For an action on several records, the dialog groups the warnings by record, each under the record's link.
- **Failure.** Any other failed confirm shows an error toast.

Theme keys: [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md) for the banner, body, and card, and [ActionForm](/v3/reference/theming/keys/ActionForm.md) for the action bar, the page gutter, and the validation summary. [ModelActionForm.card](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-card), [ModelActionForm.banner](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-banner), and [ModelActionForm.bannerIcon](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-bannerIcon) hold the per-tone treatment and switch on `page`. [ActionForm.content](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-content) sets the page gutter, and [ActionForm.stickyButtons](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-stickyButtons) lays out the action bar.

## ViewAction

[ViewAction](/v3/reference/api/vue/ViewAction.md) is the generic confirmation that [Action View Resolution](/v3/reference/glossary.md#action-view-resolution) falls back to for a model action. Its page title is the action name followed by the model's verbose name, and a "Go Back" button sits in the page actions. An action on several records uses the plural verbose name. It passes its [`pk`](/v3/reference/api/vue/ViewAction.md#prop-pk) values to the form, which looks the records up so its chips show their names. It sends the action as `PUT` unless [`requestMethod`](/v3/reference/api/vue/ModelActionForm.md#prop-requestMethod) names another method.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">action="archive" · 4 records · default info tone · live ViewAction</header>
  <ModelDemo
    :view="() => import('@vueda/views/ViewAction.vue')"
    :app="archiveScenario.app"
    :model="archiveScenario.model"
    :pk="['4', '11', '23', '1']"
    action="archive"
    :seed="archiveScenario.seed"
    :api="archiveScenario.api"
    page-title
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>tone: nothing was passed, so the card carries <code>data-tone="info"</code></span>
    <span>banner: both lines are generated; <code>bannerTitle</code> and <code>bannerDescription</code> replace them</span>
    <span>records: four chips showing primary keys, because <code>ViewAction</code> fetches nothing</span>
    <span>requests: a dry-run pre-flight when the view opens, then the action on confirm</span>
    <span>theme keys: [ViewAction](/v3/reference/theming/keys/ViewAction.md), [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md), [ActionForm](/v3/reference/theming/keys/ActionForm.md) · source: <code>ViewAction.vue</code></span>
  </footer>
</VuedaDemo>
</ClientOnly>

An action that takes input puts its fields in the [`extra-fields`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-extra-fields) slot, below the prompt panel. For the fields to be validated and sent, the view also takes [`hasInput`](/v3/reference/api/vue/ActionForm.md#prop-hasInput) and [`transformSubmitDataFn`](/v3/reference/api/vue/ModelActionForm.md#prop-transformSubmitDataFn). The demo below is the real view with the slot filled by a docs-only wrapper. Type into Reason and submit, then clear it and submit again to see the error appear under the field.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">action="duplicate" · 1 record · extra fields · live ViewAction</header>
  <ModelDemo
    :view="() => import('../../.vitepress/theme/components/DemoActionFields.vue')"
    :app="duplicateScenario.app"
    :model="duplicateScenario.model"
    pk="1"
    action="duplicate"
    :seed="duplicateScenario.seed"
    :api="duplicateScenario.api"
    page-title
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>slot: [ModelActionForm.extraFields](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-extraFields) stacks the rows below the prompt panel on the form field rhythm, so they line up with the field column of a full form</span>
    <span>fields: ordinary <code>FormField</code> rows with the form family's label, help, and error treatment; an error from local validation or from the server appears under its field</span>
    <span>input: the wrapper passes <code>hasInput</code> and <code>transformSubmitDataFn</code>, so the slotted fields join validation and become the request body</span>
  </footer>
</VuedaDemo>
</ClientOnly>

The pre-flight is a real request, so it can fail. Here the endpoint rejects the pre-flight with a message for two of the three records. The response keys each message by primary key, which is how a server reports records that cannot take the action.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">action="bill_now" · 3 records · pre-flight rejected · warning tone</header>
  <ModelDemo
    :view="() => import('@vueda/views/ViewAction.vue')"
    :app="billScenario.app"
    :model="billScenario.model"
    :pk="['4', '11', '23']"
    action="bill_now"
    :seed="billScenario.seed"
    :api="billScenario.api"
    :view-props="{ tone: 'warning' }"
    page-title
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>tone: <code>tone="warning"</code> passes through <code>ViewAction</code> to the form and switches the banner icon tile</span>
    <span>pre-flight: the 400 lands on the form as server feedback, with no toast</span>
    <span>messages: each appears beside its record's chip, so [ActionForm.validation](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-validation) has nothing left to list and stays hidden</span>
    <span>banner: its text stays generated from the action and model names; the pre-flight result shows beside the chips</span>
    <span>confirm: the confirm button in the action bar stays enabled, and the server checks the records again on confirm</span>
    <span>theme keys: [ActionForm.validation](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-validation), [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md) · source: <code>ActionForm.vue</code></span>
  </footer>
</VuedaDemo>
</ClientOnly>

## ViewActivate

[ViewActivate](/v3/reference/api/vue/ViewActivate.md) confirms the `activate` action in the `success` tone. It fetches the selected records, so each chip shows the record's formatted name as a link. It sends the action as `PATCH`. The title reads "Activate" followed by the model's verbose name unless you set the [`title`](/v3/reference/api/vue/ViewActivate.md#prop-title) prop. An action on several records uses the plural verbose name.

The server offers the `activate` action only for a model that inherits [ActivatableBaseModel](/v3/reference/api/py/vueda.core.models/ActivatableBaseModel.md), on a viewset that mixes in [DeactivateActionViewSetMixin](/v3/reference/api/py/vueda.core.viewsets/DeactivateActionViewSetMixin.md). `VuedaViewSet` does not include that mixin. The action sets `is_active` to `true` and rejects a record that is already active.

[ViewDeactivate](/v3/reference/components/action-workflow.md#viewdeactivate) shares this body ([ViewSelectedObjectsAction](/v3/reference/api/vue/ViewSelectedObjectsAction.md)) in the `warning` tone.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">action="activate" · 1 record · live ViewActivate</header>
  <ModelDemo
    :view="() => import('@vueda/views/ViewActivate.vue')"
    :app="activateScenario.app"
    :model="activateScenario.model"
    pk="1"
    action="activate"
    :seed="activateScenario.seed"
    :api="activateScenario.api"
    page-title
    toasts
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>tone: <code>data-tone="success"</code> switches the banner icon tile to the check mark</span>
    <span>banner text: the generated title and the default description; <code>bannerTitle</code> and <code>bannerDescription</code> replace them</span>
    <span>selected records: fetched by primary key; each chip is a [WidgetReadOnly](/v3/reference/api/vue/WidgetReadOnly.md) link with the formatted name</span>
    <span>prompt: a generated "Are you sure you want to ..." question; <code>confirmMessage</code> replaces the wording, and the <code>confirm-message</code> slot replaces the panel's contents</span>
    <span>requests: the same <code>PATCH</code> goes out twice, as a dry run when the view opens and as the action on confirm; the server rolls the dry run back</span>
    <span>PageTitle: supplied here by the docs harness, as an application layout would; the view contributes the title and its "Go Back" button</span>
    <span>theme keys: [ViewActivate](/v3/reference/theming/keys/ViewActivate.md), [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md), [ActionForm](/v3/reference/theming/keys/ActionForm.md) · source: <code>ViewActivate.vue</code></span>
  </footer>
</VuedaDemo>
</ClientOnly>

## ViewDeactivate

[ViewDeactivate](/v3/reference/api/vue/ViewDeactivate.md) is the default view for every model's `deactivate` route. It confirms the `deactivate` action in the `warning` tone. Like `ViewActivate`, it fetches the selected records and sends the action as `PATCH`. The title reads "Deactivate" followed by the model's verbose name unless you set the [`title`](/v3/reference/api/vue/ViewDeactivate.md#prop-title) prop. An action on several records uses the plural verbose name.

The server offers the `deactivate` action under the same conditions as `activate`: a model that inherits `ActivatableBaseModel`, on a viewset that mixes in `DeactivateActionViewSetMixin`. The action sets `is_active` to `false` and rejects a record that is already inactive. It changes nothing else, so sessions, tokens, and owned records stay as they are unless your server code changes them.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">action="deactivate" · 1 record · live ViewDeactivate</header>
  <ModelDemo
    :view="() => import('@vueda/views/ViewDeactivate.vue')"
    :app="deactivateScenario.app"
    :model="deactivateScenario.model"
    pk="1"
    action="deactivate"
    :seed="deactivateScenario.seed"
    :api="deactivateScenario.api"
    page-title
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>tone: <code>data-tone="warning"</code> switches the banner icon tile to the warning triangle</span>
    <span>text: the title, banner, and prompt come from the action and model names, so the same view fits any activatable model</span>
    <span>requests: the same <code>PATCH</code> goes out twice, as a dry run when the view opens and as the action on confirm</span>
    <span>theme keys: [ViewDeactivate](/v3/reference/theming/keys/ViewDeactivate.md), [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md), [ActionForm](/v3/reference/theming/keys/ActionForm.md) · source: <code>ViewDeactivate.vue</code></span>
  </footer>
</VuedaDemo>
</ClientOnly>

### Self-service account page

A page that lets a signed-in user deactivate their own account can wrap `ViewDeactivate`. This example targets the signed-in user and asks them to type their email address. It also replaces the default prompt with account copy:

```vue
<script setup>
import { storeUser } from "@vueda/stores/storeUser.js";
import ViewDeactivate from "@vueda/views/ViewDeactivate.vue";

const userStore = storeUser();
</script>

<template>
    <ViewDeactivate
        app="accounts"
        model="user"
        :pk="String(userStore.loggedInUser.id)"
        :confirm-text="userStore.loggedInUser.email"
        banner-title="Deactivate your account"
        action-success-summary="Account deactivated"
    >
        <template #confirm-message>
            <p>Deactivate your account? Type your email address below to confirm.</p>
        </template>
    </ViewDeactivate>
</template>
```

- Replace `accounts` and `user` with your user model's app label and model name. The viewset that serves your user model must mix in `DeactivateActionViewSetMixin`.
- [`confirmText`](/v3/reference/api/vue/ModelActionForm.md#prop-confirmText) adds a [TypedConfirmField](/v3/reference/api/vue/TypedConfirmField.md), and the confirm button stays disabled until the typed value matches. The VUEDA user model signs users in by email and requires one, so every account has a value to type.
- [`bannerTitle`](/v3/reference/api/vue/ModelActionForm.md#prop-bannerTitle) and [`actionSuccessSummary`](/v3/reference/api/vue/ModelActionForm.md#prop-actionSuccessSummary) replace the banner title and the success toast. By default both combine the action and model names.
- The [`confirm-message`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-confirm-message) slot replaces the default prompt.

The prompt describes only the action. Describe other effects, such as ending sessions, only when your server performs them.

## ViewExecuteTransition

[ViewExecuteTransition](/v3/reference/api/vue/ViewExecuteTransition.md) confirms a workflow [Transition](/v3/reference/glossary.md#transition) when the project supplies no view for its code. It renders the same confirmation as `ViewAction`, in the `info` tone, so the `ViewAction` demos above show its layout. Like `ViewAction`, its chips show the records' names from the form's lookup.

The view differs from `ViewAction` in its copy and its request:

- The page title is the transition's `name` as written, followed by the model's verbose name. Until the workflow metadata loads, the title uses the transition code in title case. The prompt uses the same name in lower case.
- Both the pre-flight and the confirm execute the transition. [Design Transition UX and Redirects](/v3/guides/transition-ux-and-redirects.md) describes the request.

The confirmation shows the transition name and the selected records only. A pre-flight rejection shows its message beside the record's chip. To show source and target states or eligibility reasons, supply a project view for the transition code.

Fields in the `extra-fields` slot render and validate, but their values are not sent with the transition.

Theme keys: [ViewExecuteTransition](/v3/reference/theming/keys/ViewExecuteTransition.md), [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md), and [ActionForm](/v3/reference/theming/keys/ActionForm.md).

## ViewHistoryList

[ViewHistoryList](/v3/reference/api/vue/ViewHistoryList.md) shows the [Model History](/v3/reference/glossary.md#model-history) of one record as the actions that produced it. The page title reads "History of" plus the model's verbose name, and a "Back" button sits in the page actions. Each action groups the events it wrote that touch the record, and each event lists its field changes. A meta strip above the grid switches between table and card layouts, and a [PaginationFooter](/v3/reference/api/vue/PaginationFooter.md) follows the grid.

The Model cell names the row that an event wrote. A related row's model name has its object id beside it. A workflow state event reads "Workflow state". [Model History Response](/v3/core-concepts/model-history-response.md) describes which events the response includes and how they group.

In the table layout, each field change is one row. The first row of an action carries the action's When, Who, Kind, and Action cells. The first row of each event carries its Model and Type cells. An action's first row has `data-rev-start`, its other rows have `data-rev-child`, and each event's first row has `data-event-start`. [ViewHistoryList.row](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-row) draws the action stripe from these attributes, so the grouping survives a reskin.

In the card layout, each event is one card. Its changes stack in the Old and New cells, each value labeled with its field name.

The demo below is the live component against an offline history endpoint. Use the Table and Cards buttons to switch layouts.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">model="customer" · pk=1 · 5 actions · live ViewHistoryList</header>
  <ModelDemo
    :view="() => import('@vueda/views/ViewHistoryList.vue')"
    :app="historyScenario.app"
    :model="historyScenario.model"
    pk="1"
    action="history_list"
    :seed="historyScenario.seed"
    :api="historyScenario.api"
    page-title
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>columns: the history response defines them, and the view labels them; the <code>fields</code> prop picks and orders from <code>recorded_at</code>, <code>actor</code>, <code>kind</code>, <code>label</code>, <code>model</code>, <code>relation</code>, <code>type</code>, <code>field</code>, <code>old</code>, and <code>new</code>; the default leaves out <code>relation</code></span>
    <span>diff: [ViewHistoryList.diff](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-diff) styles old and new values apart by <code>data-side</code>, and an empty side by <code>data-empty</code></span>
    <span>type pill: <code>created</code>, <code>updated</code>, and <code>deleted</code> each get a tone and an icon; any other type renders as plain text</span>
    <span>kind pill: neutral, with added emphasis for the kinds VUEDA defines; any kind renders as written</span>
    <span>no field changes: the Field cell reads "(created)", "(deleted)", or "(no field changes)"</span>
    <span>references: a field that points at another row shows that row's current name; a row that no longer exists reads "deleted row", and a deleted actor reads "deleted user", each followed by its id when the server sends one</span>
    <span>dates: an absolute date and time, with a relative phrase below it</span>
    <span>layout: the Table and Cards buttons pin a layout; until one is pressed, the <code>tableBreakpoint</code> prop picks it</span>
    <span>theme keys: [ViewHistoryList](/v3/reference/theming/keys/ViewHistoryList.md), [ObjectsGrid](/v3/reference/theming/keys/ObjectsGrid.md) · source: <code>ViewHistoryList.vue</code></span>
  </footer>
</VuedaDemo>
</ClientOnly>

A record with no history shows an empty state in place of the meta strip and the grid.

<ClientOnly>
<VuedaDemo class="flex flex-col gap-3">
  <header class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">model="customer" · pk=2 · no history yet</header>
  <ModelDemo
    :view="() => import('@vueda/views/ViewHistoryList.vue')"
    :app="emptyHistoryScenario.app"
    :model="emptyHistoryScenario.model"
    pk="2"
    action="history_list"
    :seed="emptyHistoryScenario.seed"
    :api="emptyHistoryScenario.api"
    page-title
  />
  <footer class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
    <span>empty state: "No history yet" with a short description; the <code>empty</code> slot replaces its contents</span>
    <span>loading: while the request runs, the grid stays up with its skeleton rows; the empty state appears once zero rows are confirmed</span>
    <span>pagination: the footer stays</span>
  </footer>
</VuedaDemo>
</ClientOnly>

## Customization surface

`ViewAction`, `ViewActivate`, `ViewDeactivate`, and `ViewExecuteTransition` pass their attributes and slots through to `ModelActionForm`, so these props and slots apply to all of them:

- [`bannerTitle`](/v3/reference/api/vue/ModelActionForm.md#prop-bannerTitle) and [`bannerDescription`](/v3/reference/api/vue/ModelActionForm.md#prop-bannerDescription) replace the banner lines. The [`action-banner`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-action-banner) slot replaces the whole banner, and [`banner-meta`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-banner-meta) adds a line below the description.
- [`selected-objects`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-selected-objects) replaces the contents of the selected-records panel, for a project that wants richer rows than a name and a primary key. [`link-item`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-link-item) replaces the link in each chip.
- [`confirmMessage`](/v3/reference/api/vue/ModelActionForm.md#prop-confirmMessage) replaces the prompt wording, and the [`confirm-message`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-confirm-message) slot replaces the prompt panel's contents.
- `extra-fields` adds fields below the prompt panel.
- [`confirmText`](/v3/reference/api/vue/ModelActionForm.md#prop-confirmText) adds a [TypedConfirmField](/v3/reference/api/vue/TypedConfirmField.md) and keeps the confirm button disabled until the typed text matches.
- [`actions-hint`](/v3/reference/api/vue/ActionForm/slots.md#slot-actions-hint) fills the right side of the action bar, for a shortcut or an audit note. [`confirm-button`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-confirm-button) replaces the confirm button, including its label. On a page, the [`return-button`](/v3/reference/api/vue/ViewAction.md#slot-return-button) slot replaces the "Go Back" button.
- [`validation-summary`](/v3/reference/api/vue/ActionForm/slots.md#slot-validation-summary) replaces the list of errors that no field shows. [`warning-entry`](/v3/reference/api/vue/ModelActionForm/slots.md#slot-warning-entry) replaces one field's warnings in the confirmation dialog.

The default slot of `ViewAction` replaces the whole confirmation.

`ViewHistoryList` takes [`hideMetaStrip`](/v3/reference/api/vue/ViewHistoryList.md#prop-hideMetaStrip) to hide the meta strip. Its [slots](/v3/reference/api/vue/ViewHistoryList/slots.md) replace the meta strip, add filter chips to it, replace the empty state, and replace the cells of the When, Who, Kind, Model, Type, Old, and New columns.

Theme keys by area:

- [ModelActionForm](/v3/reference/theming/keys/ModelActionForm.md): the card, banner, and icon tile per tone, each with a page variant ([ModelActionForm.card](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-card), [ModelActionForm.banner](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-banner), [ModelActionForm.bannerIcon](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-bannerIcon)), the selected-records panel and chips ([ModelActionForm.selectedObjects](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-selectedObjects), [ModelActionForm.listItem](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-listItem)), the prompt panel ([ModelActionForm.message](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-message)), and the warning groups in the confirmation dialog ([ModelActionForm.confirmWarningGroup](/v3/reference/theming/keys/ModelActionForm.md#theme-key-ModelActionForm-confirmWarningGroup)).
- [ActionForm](/v3/reference/theming/keys/ActionForm.md): the page gutter ([ActionForm.content](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-content)), the action bar on a page ([ActionForm.stickyButtons](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-stickyButtons)), the actions strip on a card ([ActionForm.buttons](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-buttons)), and the validation summary ([ActionForm.validation](/v3/reference/theming/keys/ActionForm.md#theme-key-ActionForm-validation)).
- [ViewHistoryList](/v3/reference/theming/keys/ViewHistoryList.md): the action stripe ([ViewHistoryList.row](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-row)), the diff values ([ViewHistoryList.diff](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-diff)), the pills ([ViewHistoryList.typePill](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-typePill), [ViewHistoryList.kindPill](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-kindPill)), the meta strip ([ViewHistoryList.meta](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-meta)), and the empty state ([ViewHistoryList.empty](/v3/reference/theming/keys/ViewHistoryList.md#theme-key-ViewHistoryList-empty)).
- [ViewAction](/v3/reference/theming/keys/ViewAction.md), [ViewActivate](/v3/reference/theming/keys/ViewActivate.md), [ViewDeactivate](/v3/reference/theming/keys/ViewDeactivate.md), and [ViewExecuteTransition](/v3/reference/theming/keys/ViewExecuteTransition.md): each view's outer wrapper.

Tokens:

- [--info](/v3/reference/theming/tokens/color-palette-light.md#css-token-info), [--success](/v3/reference/theming/tokens/color-palette-light.md#css-token-success), [--warning](/v3/reference/theming/tokens/color-palette-light.md#css-token-warning), and [--destructive](/v3/reference/theming/tokens/color-palette-light.md#css-token-destructive) color the banner and icon tile of their tone. On a card, the `success`, `warning`, and `danger` tones also color the card edge.
- [--primary](/v3/reference/theming/tokens/color-palette-light.md#css-token-primary) draws the card prompt panel's rule and the history action stripe.
