# Workflow

> A complete screen built from Badge, Button, Flow, IconButton and 6 more components.

Pattern · Pro kit · [Live page](https://ui.twentyfirst.media/pro/patterns/WorkflowScreen/)

## Code

```tsx
import * as React from "react";
import {
  Badge,
  Button,
  Flow,
  IconButton,
  ListRow,
  PageHeader,
  Stagger,
  TintIcon,
} from "@twentyfirstmedia/pro";
import { Shell } from "./_workspace";

function App() {
  const run = React.useState<number | null>(null);
  React.useEffect(() => {
    if (run[0] == null || run[0] === 3 || run[0] >= 5) return;
    const t = setTimeout(() => {
      run[1]((run[0] ?? 0) + 1);
    }, 800);
    return () => {
      clearTimeout(t);
    };
  }, [run[0]]);
  return (
    <Shell
      area="Agents"
      current="workflows"
      action={{
        label: "New agent",
        icon: "plus",
      }}
      pages={[
        {
          id: "fleet",
          label: "Fleet",
          icon: "bot",
        },
        {
          id: "chat",
          label: "Chat",
          icon: "mail",
        },
        {
          id: "workflows",
          label: "Workflows",
          icon: "zap",
        },
        {
          id: "runs",
          label: "Runs",
          icon: "refresh",
          count: 2,
        },
        {
          id: "knowledge",
          label: "Knowledge",
          icon: "book",
        },
        {
          id: "usage",
          label: "Usage & cost",
          icon: "chart",
        },
      ]}
    >
      <div className="tf-in">
        <PageHeader
          title="Workflows"
          subtitle="Chain agents, approvals and actions"
          actions={[
            <Button key="n" icon="plus">
              New workflow
            </Button>,
          ]}
        />
      </div>
      <div
        className="tf-grid"
        style={
          {
            "--cols": "minmax(0, 300px) minmax(0, 1fr)",
            gap: 16,
            alignItems: "start",
          } as React.CSSProperties
        }
      >
        <div
          className="tf-panel tf-panel-flush tf-in"
          style={
            {
              "--d": "80ms",
            } as React.CSSProperties
          }
        >
          <Stagger className="tf-list">
            <ListRow
              active
              leading={<TintIcon icon="zap" tint="mint" />}
              title="Support replies"
              subtitle="5 steps · 214 runs"
              trailing={<Badge tone="success">On</Badge>}
            />
            <ListRow
              leading={<TintIcon icon="zap" tint="mint" />}
              title="Receipt to booking"
              subtitle="5 steps · 96 runs"
              trailing={<Badge tone="success">On</Badge>}
            />
            <ListRow
              leading={<TintIcon icon="zap" tint="plain" />}
              title="Lead research"
              subtitle="3 steps · 37 runs"
              trailing={<Badge>Off</Badge>}
            />
          </Stagger>
        </div>
        <div
          className="tf-panel tf-in"
          style={
            {
              "--d": "160ms",
              gap: 16,
            } as React.CSSProperties
          }
        >
          <div
            className="tf-header"
            style={{
              alignItems: "center",
            }}
          >
            <h2 className="tf-panel-title">Support replies</h2>
            <Button
              variant="soft"
              icon="play"
              disabled={run[0] != null && run[0] < 5}
              onClick={() => {
                run[1](0);
              }}
            >
              {run[0] != null && run[0] < 5 ? "Running…" : "Test run"}
            </Button>
          </div>
          <Flow
            active={run[0]}
            waiting={run[0] === 3}
            onApprove={() => {
              run[1](4);
            }}
            onReject={() => {
              run[1](null);
            }}
            tools={() => [
              <IconButton key="e" icon="edit" label="Edit step" size="sm" />,
              <IconButton key="t" icon="trash" label="Remove step" size="sm" />,
            ]}
            steps={[
              {
                kind: "trigger",
                text: "New email arrives in Inbox",
              },
              {
                kind: "condition",
                text: "Label is Customers",
              },
              {
                kind: "agent",
                text: "Atlas drafts a reply from the knowledge base",
              },
              {
                kind: "approval",
                text: "Mara approves the draft",
              },
              {
                kind: "action",
                text: "Send the reply and log it on the company",
              },
            ]}
          />
        </div>
      </div>
    </Shell>
  );
}
export default function Preview() {
  return <App />;
}
```

## Props

### Badge

| Prop | Type | Notes |
| --- | --- | --- |
| `tone?` | `Tone` |  |
| `dot?` | `boolean` |  |
| `live?` | `boolean` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Button

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `"primary" \| "secondary" \| "soft" \| "ghost" \| "danger" \| "danger-solid"` |  |
| `size?` | `"md" \| "sm"` |  |
| `icon?` | `IconName` |  |
| `iconRight?` | `IconName` |  |
| `disabled?` | `boolean` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `asChild?` | `boolean` | Render the single child element (a link, a router Link) with the button's look instead of a <button>. |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Flow

| Prop | Type | Notes |
| --- | --- | --- |
| `steps` | `FlowStep[]` | Required |
| `active?` | `number \| null` |  |
| `waiting?` | `boolean` |  |
| `onApprove?` | `() => void` |  |
| `onReject?` | `() => void` |  |
| `tools?` | `(step: FlowStep, index: number) => ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Icon

| Prop | Type | Notes |
| --- | --- | --- |
| `name` | `IconName` | Required |
| `size?` | `number` |  |
| `label?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` | Inline style on the root element. |

### IconButton

| Prop | Type | Notes |
| --- | --- | --- |
| `icon` | `IconName` | Required |
| `label` | `string` | Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them. |
| `variant?` | `"soft" \| "solid" \| "outline" \| "ghost"` |  |
| `size?` | `"sm" \| "md" \| "lg"` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `disabled?` | `boolean` |  |
| `asChild?` | `boolean` | Render the single child element (a link) with the button's look; the icon is placed inside it. |
| `children?` | `ReactNode` |  |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### ListRow

| Prop | Type | Notes |
| --- | --- | --- |
| `avatar?` | `string` |  |
| `icon?` | `IconName` |  |
| `tint?` | `Tint \| "plain"` |  |
| `leading?` | `ReactNode` |  |
| `title` | `ReactNode` | Required |
| `subtitle?` | `ReactNode` |  |
| `meta?` | `ReactNode` |  |
| `trailing?` | `ReactNode` |  |
| `unread?` | `boolean` |  |
| `active?` | `boolean` |  |
| `onClick?` | `() => void` |  |
| `href?` | `string` | Makes the row a link (with `linkAs` for a router's link). |
| `linkAs?` | `ElementType` |  |
| `actions?` | `ReactNode` | Buttons of the row's own (rename, remove …), outside the clickable area so nothing interactive is nested. Without `onClick` and `href` the row is not interactive at all. |
| `titleLines?` | `number` | Lines the title may take before it ends in an ellipsis (default: it wraps freely). |
| `subtitleLines?` | `number` | Lines the subtitle may take (default 1); badges and facts wrap within them. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### PageHeader

| Prop | Type | Notes |
| --- | --- | --- |
| `crumbs?` | `(string \| Crumb)[]` | A breadcrumb above the title (see `Breadcrumb`): labels, or items with `href`/`onClick`. |
| `crumbLinkAs?` | `ElementType` | The element crumb links render as, e.g. Next.js `Link`. |
| `title` | `string` | Required |
| `subtitle?` | `string` |  |
| `actions?` | `ReactNode[]` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Stagger

| Prop | Type | Notes |
| --- | --- | --- |
| `as?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` |  |
| `delay?` | `number` | Extra delay before the first child, in ms. |
| `reveal?` | `boolean` | Below the fold, wait until the group scrolls into view (default true). |
| `children?` | `ReactNode` |  |

### SubNav

| Prop | Type | Notes |
| --- | --- | --- |
| `eyebrow?` | `string` |  |
| `action?` | `{ label: string; icon?: IconName; onClick?: () => void }` |  |
| `items` | `SubNavItem[]` | Required |
| `current?` | `string` |  |
| `onPick?` | `(id: string) => void` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### TintIcon

| Prop | Type | Notes |
| --- | --- | --- |
| `icon` | `IconName` | Required |
| `tint?` | `Tint \| "plain"` |  |
| `label?` | `string` |  |
| `size?` | `number` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### TopBar

| Prop | Type | Notes |
| --- | --- | --- |
| `brand?` | `ReactNode` | The product name, or a node (a logo mark with the name). |
| `tag?` | `string` |  |
| `items` | `NavItem[]` | Required |
| `active?` | `string` |  |
| `user?` | `{ name: string; email?: string }` |  |
| `onNavigate?` | `(label: string) => void` |  |
| `extra?` | `ReactNode` |  |
| `userOpen?` | `boolean` |  |
| `onUser?` | `() => void` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
