Workflows

Reusable UI components

Add assignment, date, member, and workflow-diagram controls to a custom Workflows interface.

Workflows exposes reusable React components in two packages. @sanity/workflow-components contains member, assignee, and date controls. @sanity/workflow-diagram renders a workflow definition and its live evaluation. They work in App SDK applications, Studio integrations, and other React hosts.

These packages provide presentation, not an application framework. Your host supplies project members and workflow data, chooses which evaluated fields to render, and connects edits and actions through the reactive session.

Install the packages

Install the components your interface uses:

Install the Workflows packages from one matching release.

The components use Sanity UI and must render under its theme provider:

import {studioTheme, ThemeProvider} from '@sanity/ui'

<ThemeProvider theme={studioTheme}>
  <WorkflowInterface />
</ThemeProvider>

Components

Choose a component by the interaction your interface needs. Each table lists the complete public props for that component family.

A project role is an assignable project-level role, even when no current member holds it. Workflow assignees store its machine name; presentation can use the optional project title.

Assignment badges

Use badges to display stored user and role assignees.

import {AssigneeBadges} from '@sanity/workflow-components'

<AssigneeBadges assignees={assignees} vocabulary={{members, roles}} />

Assignment stacks

Use AssigneeStack to present one scope’s complete assignment in a dense row. People render as round avatars, roles as square avatars, and optional glyphs after both. Glyphs remain visible when avatars overflow; the caller supplies full member and role names in surrounding or hover content.

import {AssigneeStack} from '@sanity/workflow-components'

<AssigneeStack members={assignedMembers} roles={assignedRoles} />
  • members

    readonly {id: string; displayName: string; imageUrl?: string; loginProvider?: string}[]

    Resolved members to show as round avatars.

  • roles

    readonly MemberRole[]

    Project roles to show as square avatars. The role title supplies the initials and tooltip; the machine name supplies the stable color.

  • glyphs

    readonly {icon: ReactNode; key: string}[]

    Optional trailing glyphs. They render after members and roles and remain visible when avatars overflow.

Member and role avatars

Use MemberAvatar for a resolved project member and RoleAvatar for a project role. Missing member images fall back to initials. Role avatars are square and use the project title when available. A direct RoleAvatar host must provide its own tooltip and accessible name; AssigneeStack already names the group.

import {MemberAvatar, MemberAvatarGroup} from '@sanity/workflow-components'

<MemberAvatar imageUrl={member.imageUrl} name={member.displayName} />
<MemberAvatarGroup members={members} />
  • Display name used for initials and accessible presentation.

  • MemberAvatar.imageUrl

    string | undefined

    Optional profile image URL.

  • MemberAvatar.loginProvider

    string | undefined

    Optional sign-in provider badge. Google, GitHub, and saml-prefixed providers have marks; unrecognized providers render no badge.

  • MemberAvatar.size

    AvatarSize | AvatarSize[] | undefined

    Optional Sanity UI avatar size. Defaults to 0.

  • RoleAvatar.role

    MemberRole

    The project role to render. A direct host must wrap the avatar with its own tooltip and accessible name.

  • RoleAvatar.size

    AvatarSize | AvatarSize[] | undefined

    Optional Sanity UI avatar size.

  • MemberAvatarGroup.members

    readonly {id: string; displayName: string; imageUrl?: string}[]

    Members with id, displayName, and optional imageUrl.

  • MemberAvatarGroup.maxLength

    number | undefined

    Maximum visible avatars. Defaults to 3.

Member picker

Use MemberPicker for choosing one project member.

import {MemberPicker} from '@sanity/workflow-components'

<MemberPicker
  {...members}
  onSelect={setMember}
  selectedIds={selectedIds}
/>

Assignee picker

Use AssigneePicker when an assignees field accepts both project members and project roles.

import {AssigneePicker} from '@sanity/workflow-components'

<AssigneePicker
  {...members}
  onChange={setAssignees}
  value={assignees}
/>

Pickers sort people by display name and search names, email addresses, role names, and role titles. Hovering a person shows their email, identity-provider badge, current-user status, and project roles. Selection does not imply eligibility: activity and action role gates do not rank or filter the choices.

A non-empty role catalog controls the offered roles, including roles with no holders. If the catalog is empty or unavailable, the picker uses member-held roles. A role already stored in the value remains visible and removable.

Date controls

Use DatePicker for selection only. ClearableDatePicker adds an explicit clear action.

import {ClearableDatePicker, DatePicker} from '@sanity/workflow-components'

<DatePicker onSelect={setDate} value={date} />

<ClearableDatePicker
  clearLabel="Clear due date"
  onClear={() => setDueDate(undefined)}
  onPick={setDueDate}
  value={dueDate}
/>

Progress

Use ProgressBar to display a progress field. It does not edit the field.

import {ProgressBar} from '@sanity/workflow-components'

<ProgressBar label="Content import" value={62.5} />

Supply project members

Member-aware components consume ProjectMembersState, which contains members, the project role catalog, and loading state. App SDK applications can load it with useProjectMembers(projectId); see Edit project-member assignments.

The selected directory is project-scoped. Each ProjectMember.id is the account-global sanityUserId stored in user assignees.

Role titles are presentation data. An assignee value continues to store the machine role name, so changing a project role's title does not change persisted workflow data.

Pass the resolved state to any member-aware component. The component renders loading and error states from the same object.

Connect editable fields

Editable controls render values from session.evaluation.editableFields. Commit changes through session.editField so the engine preserves workflow history and consistency. The App SDK guide shows the complete pattern. Pass the evaluated field directly to session.editField; editFieldTarget is for adapter-level controls.

The recipes connect these presentation components to evaluated fields and session commits. The wrappers shown below belong to the application, not the component package.

export function WorkflowEditableFields({
  evaluation,
  members,
  session,
}: {
  evaluation: WorkflowEvaluation
  members: ProjectMembersState
  session: Pick<WorkflowSession, 'editField'>
}) {
  return evaluation.editableFields.map((field) => {
    const key = `${field.scope}:${field.activity ?? ''}:${field.name}`
    if (isAssignmentField(field)) {
      return <AssignmentPicker field={field} key={key} members={members} session={session} />
    }
    if (isDateField(field)) {
      return <DateField field={field} key={key} session={session} />
    }
    return null
  })
}

The complete recipe includes the value-shape guards used by isAssignmentField and isDateField.

The component recipes contain the complete assignment, date parsing, serialization, mutation, and error-handling code.

Render an enabled editor only when the evaluated field is editable. Surface rejected commits instead of assuming an optimistic edit succeeded.

Render the workflow diagram

Pass the session’s WorkflowEvaluation to WorkflowDiagram. The pinned definition describes the graph, currentStage marks the active node, history draws the visited path, and evaluation supplies live explanations.

Loading...
import {Card, Stack, Text} from '@sanity/ui'
import {WorkflowDiagram} from '@sanity/workflow-diagram'
import type {WorkflowEvaluation} from '@sanity/workflow-engine'

export function WorkflowDiagramRecipe({evaluation}: {evaluation: WorkflowEvaluation}) {
  return (
    <Card border padding={4} radius={3}>
      <Stack gap={4}>
        <Stack gap={2}>
          <Text size={2} weight="semibold">
            Workflow diagram
          </Text>
          <Text muted size={1}>
            Current stage and visited path
          </Text>
        </Stack>
        <WorkflowDiagram
          currentStage={evaluation.instance.currentStage}
          definition={evaluation.definition}
          evaluation={evaluation}
          explain
          history={evaluation.instance.history}
          key={evaluation.instance._id}
        />
      </Stack>
    </Card>
  )
}
  • definition

    WorkflowDefinition

    Complete workflow definition to render.

  • currentStage

    string | undefined

    Stage to highlight as current.

  • history

    readonly HistoryEntry[] | undefined

    Tones visited stages and traversed transitions.

  • gatedTransitions

    readonly string[] | undefined

    Transition names that should show a locked marker.

  • local

    LocalView | undefined

    Limits the graph to context around the current stage.

  • static

    boolean | undefined

    Disables pan, zoom, and zoom controls.

  • devMode

    boolean | undefined

    Shows machine names and raw conditions.

  • explain

    boolean | undefined

    Shows human-readable gate explanations.

  • evaluation

    WorkflowEvaluation | undefined

    Adds live verdicts when explain is enabled.

  • height

    number | string | undefined

    Maximum canvas height. Defaults to 360.

  • fill

    boolean

    Keep the canvas at the parent’s full width and use the supplied height. The graph is centered within the available space and is never enlarged beyond 1:1. Defaults to false.

The diagram uses color, line style, and opacity to distinguish run state. The current stage carries the accent color. Completed stages carry checkmarks. Traversed transitions are solid, while transitions not taken are dashed. Unvisited stages that can no longer be reached from the current stage fade with their transitions.

Hovering a stage restores its immediate neighborhood and animates dashed outgoing transitions toward their targets. Hovering a transition animates that transition. The animation is disabled when the user prefers reduced motion.

Diagram theming

WS_CARD_TOKENS maps diagram colors to the surrounding Sanity UI card. The 0.24 token map removes --ws-red and --ws-font and replaces --ws-positive with --ws-path.

Use --ws-path to customize the traversed path. If a custom wrapper reuses WS_CARD_TOKENS and needs explicit typography variables, set --ws-font and --ws-font-weight on that wrapper or derive them from useTheme_v2().font.text.

Key the diagram by instance ID so each opened workflow receives a fresh fitted viewport.

Enable explain to show gate explanations. Pass the live evaluation to add current verdicts.

Next steps

  • App SDK: build the instance-list and task-session application around these controls.
  • Fields: understand editable field types, scopes, and stored values.
  • History and audit trail: use instance history when presenting the workflow’s visited path.

Visiting agent?

Was this page helpful?