Live CortexUI Surface

This block renders live CortexUI contract metadata in the docs DOM so AI View can inspect real machine-readable elements instead of only code examples.

AI View can now inspect a live status region, form fields, actions, and table entities on every docs page.
AI-addressable docs entities
ItemState
Search docsReady
Inspect metadataVisible in AI View

Action-Driven Design

Action-driven design is a UI methodology where interfaces are organized around what they allow you to do, rather than how they look. It is the conceptual foundation of CortexUI's AI contract.

Traditional Design vs. Action-Driven Design

In traditional UI design, a screen is organized around its visual layout. A designer asks: "Where should the form go? What color is the button? What typography hierarchy do we use?" The deliverable is a visual composition.

In action-driven design, a screen is organized around its operational vocabulary. A designer asks: "What can the user do here? What operations does this screen expose? What is the consequence of each?" The deliverable is an action catalog — a list of every intent the screen supports.

TraditionalAction-Driven
Organized around layoutOrganized around operations
Primary question: "How does this look?"Primary question: "What can you do here?"
State lives in CSS classesState lives in data-ai-state
Intent is inferred from visualsIntent is declared in data-ai-action
AI must scrape and inferAI reads the contract directly

The visual design still matters — but it is the presentation layer on top of an explicit operational model.

The Action Vocabulary

Every screen in an action-driven application has a catalog of actions. Before implementing any UI, enumerate all the things the user should be able to do on that screen.

For a CRM user profile page, the action vocabulary might be:

Screen: user-profile
Entity: user / user-{id}

Actions:
  update-avatar         → Replace the user's profile image
  save-profile          → Persist edits to name, email, phone
  cancel-edit           → Discard unsaved form changes
  assign-role           → Change the user's permission role
  resend-invitation     → Re-send the email invitation
  suspend-account       → Temporarily disable the account
  reactivate-account    → Re-enable a suspended account
  export-audit-log      → Download activity log as CSV
  delete-account        → Permanently remove the account

This catalog becomes the contract. Everything in the UI is in service of enabling these actions — and every action must be annotated in the DOM.

Naming Convention: Verb-Noun

Action names follow a strict verb-noun convention in kebab-case. This is not stylistic preference — it is a contract requirement. The verb tells agents what kind of operation this is; the noun tells them what it operates on.

save-profile         verb: save    noun: profile
delete-account       verb: delete  noun: account
assign-role          verb: assign  noun: role
export-audit-log     verb: export  noun: audit-log
resend-invitation    verb: resend  noun: invitation

Consistent verb vocabulary matters too. Choose a standard set of verbs for your domain and stick to them across the entire application:

VerbMeaning
savePersist edits to an existing record
createAdd a new record
deletePermanently remove a record
archiveSoft-remove a record
submitFinalize a transactional form
updateModify a specific property
exportGenerate a downloadable file
importUpload external data
assignLink one entity to another
revokeRemove a link or permission
Important

Action names are part of your public API surface if you expose AI-operable interfaces. Changing "save-profile" to "update-profile" in a future release is a breaking change for any agent or automation that references the old name. Treat action names with the same stability contract as REST endpoint paths.

Mapping a CRM Page's Actions

Here is how to map actions for a full CRM user profile page, from vocabulary to annotated HTML:

Step 1: List the actions

update-avatar, save-profile, cancel-edit, assign-role,
resend-invitation, suspend-account, delete-account

Step 2: Group by section

profile-header:   update-avatar
profile-form:     save-profile, cancel-edit
account-settings: assign-role
account-actions:  resend-invitation, suspend-account
danger-zone:      delete-account

Step 3: Annotate the DOM

<div data-ai-screen="user-profile" data-ai-entity="user" data-ai-entity-id="user-abc">

  <section data-ai-section="profile-header">
    <button data-ai-role="action" data-ai-id="update-avatar" data-ai-action="update-avatar" data-ai-state="idle">
      Update Avatar
    </button>
  </section>

  <section data-ai-section="profile-form">
    <form data-ai-role="form" data-ai-id="edit-profile-form">
      <!-- fields -->
      <button data-ai-role="action" data-ai-id="save-profile" data-ai-action="save-profile" data-ai-state="idle" type="submit">
        Save Profile
      </button>
      <button data-ai-role="action" data-ai-id="cancel-edit" data-ai-action="cancel-edit" data-ai-state="idle" type="button">
        Cancel
      </button>
    </form>
  </section>

  <section data-ai-section="account-settings">
    <button data-ai-role="action" data-ai-id="assign-role" data-ai-action="assign-role" data-ai-state="idle">
      Assign Role
    </button>
  </section>

  <section data-ai-section="account-actions">
    <button data-ai-role="action" data-ai-id="resend-invitation" data-ai-action="resend-invitation" data-ai-state="idle">
      Resend Invitation
    </button>
    <button data-ai-role="action" data-ai-id="suspend-account" data-ai-action="suspend-account" data-ai-state="idle">
      Suspend Account
    </button>
  </section>

  <section data-ai-section="danger-zone">
    <button data-ai-role="action" data-ai-id="delete-account" data-ai-action="delete-account" data-ai-state="idle">
      Delete Account
    </button>
  </section>

</div>

role="action" vs role="field"

Actions and fields represent fundamentally different interaction modes:

role="action"role="field"
PurposeTriggers an operationAccepts a value
Agent behaviorClicks/activates itWrites a value to it
State lifecycleidle → loading → success/erroridle → error (on validation)
Key attributedata-ai-actiondata-ai-field-type
Emits eventaction_triggered, action_completedfield_updated

A submit button is an action. A text input is a field. They work together — the field collects data, the action uses it.

Note

Never use role="action" on an element that just updates local UI state without a meaningful async operation. Opening a dropdown, scrolling to a section, or toggling a collapsed panel are not "actions" in the contract sense — they are UI interactions. Reserve role="action" for meaningful operations: saves, deletes, submissions, exports.

How Action-Driven Design Makes Testing Easier

Because every operation has a stable name, tests can reference actions by intent rather than by DOM structure:

// Before: fragile test that references DOM structure
const saveButton = screen.getByRole("button", { name: /save/i });
fireEvent.click(saveButton);

// After: semantic test that references the contract
const saveButton = document.querySelector('[data-ai-action="save-profile"]');
fireEvent.click(saveButton!);

// Or with the testing utilities
await triggerAction("save-profile");
expect(await getActionState("save-profile")).toBe("success");

Tests written against the action contract survive visual redesigns. You can rename the button from "Save" to "Update Profile" and the test still passes — because it is testing the action, not the label.

Action-driven design also makes end-to-end test scripts self-documenting:

// The intent is clear from the action names alone
await triggerAction("save-profile");
await waitForActionState("save-profile", "success");
await triggerAction("assign-role");
await waitForActionState("assign-role", "success");
await triggerAction("resend-invitation");