Wizard

A controlled staged workflow shell with ordered step navigation, current content and an Action Bar. The host supplies stage content, validation, saving, availability and navigation. Completed stages show a check; current and upcoming stages show their number. Mark inaccessible future stages disabled. Optional hrefs preserve native navigation before JavaScript. The navigate event reports the requested value, previous value and trigger without changing value. The default slot contains the current stage; secondary and primary slots compose Action Bar for host-supplied Back, Continue or Finish actions. The disabled prop affects step navigation; the host disables its own content and actions when needed. Shared theme tokens provide palette-aware states and the step list wraps at narrow widths.

Staged workflow

The host marks completed and inaccessible stages, supplies the current stage content, and owns the Back and Continue actions.

Add details

Enter the details for this stage, then continue to review.

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-wizard/ui-wizard.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-input/ui-input.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-wizard
  value="details"
  label="Setup progress"
  steps="[{&quot;value&quot;:&quot;start&quot;,&quot;label&quot;:&quot;Start&quot;,&quot;complete&quot;:true},{&quot;value&quot;:&quot;details&quot;,&quot;label&quot;:&quot;Details&quot;},{&quot;value&quot;:&quot;review&quot;,&quot;label&quot;:&quot;Review&quot;,&quot;disabled&quot;:true},{&quot;value&quot;:&quot;finish&quot;,&quot;label&quot;:&quot;Finish&quot;,&quot;disabled&quot;:true}]"
>
  <ui-stack
    gap="s"
  >
    <ui-text
      as="h2"
      size="lg"
    >Add details</ui-text>
    <ui-text>Enter the details for this stage, then continue to review.</ui-text>
    <ui-input
      label="Display name"
      value="Example workspace"
    ></ui-input>
  </ui-stack>
  <ui-button
    slot="secondary"
  >Back</ui-button>
  <ui-button
    slot="primary"
    preset="primary"
  >Continue</ui-button>
</ui-wizard>
<script setup>
  import { UiButton, UiInput, UiStack, UiText, UiWizard } from "@threadlabs/looma";
</script>

<template>
  <UiWizard
    value="details"
    label="Setup progress"
    :steps="[{ value: 'start', label: 'Start', complete: true }, { value: 'details', label: 'Details' }, { value: 'review', label: 'Review', disabled: true }, { value: 'finish', label: 'Finish', disabled: true }]"
  >
    <UiStack
      gap="s"
    >
      <UiText
        as="h2"
        size="lg"
      >Add details</UiText>
      <UiText>Enter the details for this stage, then continue to review.</UiText>
      <UiInput
        label="Display name"
        value="Example workspace"
      />
    </UiStack>
    <template #secondary>
      <UiButton>Back</UiButton>
    </template>
    <template #primary>
      <UiButton
        preset="primary"
      >Continue</UiButton>
    </template>
  </UiWizard>
</template>
import { UiButton, UiInput, UiStack, UiText, UiWizard } from "@threadlabs/looma";

export default function Example() {
  return <UiWizard
    value="details"
    label="Setup progress"
    steps={[{"value":"start","label":"Start","complete":true},{"value":"details","label":"Details"},{"value":"review","label":"Review","disabled":true},{"value":"finish","label":"Finish","disabled":true}]}
    slots={{
      secondary: <UiButton>Back</UiButton>,
      primary: <UiButton
        preset="primary"
      >Continue</UiButton>,
    }}
  >
    <UiStack
      gap="s"
    >
      <UiText
        as="h2"
        size="lg"
      >Add details</UiText>
      <UiText>Enter the details for this stage, then continue to review.</UiText>
      <UiInput
        label="Display name"
        value="Example workspace"
      />
    </UiStack>
  </UiWizard>;
}
/** @jsxImportSource preact */
import { UiButton, UiInput, UiStack, UiText, UiWizard } from "@threadlabs/looma";

export default function Example() {
  return <UiWizard
    value="details"
    label="Setup progress"
    steps={[{"value":"start","label":"Start","complete":true},{"value":"details","label":"Details"},{"value":"review","label":"Review","disabled":true},{"value":"finish","label":"Finish","disabled":true}]}
    slots={{
      secondary: <UiButton>Back</UiButton>,
      primary: <UiButton
        preset="primary"
      >Continue</UiButton>,
    }}
  >
    <UiStack
      gap="s"
    >
      <UiText
        as="h2"
        size="lg"
      >Add details</UiText>
      <UiText>Enter the details for this stage, then continue to review.</UiText>
      <UiInput
        label="Display name"
        value="Example workspace"
      />
    </UiStack>
  </UiWizard>;
}
<script>
  import { UiButton, UiInput, UiStack, UiText, UiWizard } from "@threadlabs/looma";
</script>

{#snippet secondary()}
  <UiButton>Back</UiButton>
{/snippet}

{#snippet primary()}
  <UiButton
    preset="primary"
  >Continue</UiButton>
{/snippet}

<UiWizard
  value="details"
  label="Setup progress"
  steps={[{"value":"start","label":"Start","complete":true},{"value":"details","label":"Details"},{"value":"review","label":"Review","disabled":true},{"value":"finish","label":"Finish","disabled":true}]}
  slots={{ secondary, primary }}
>
  <UiStack
    gap="s"
  >
    <UiText
      as="h2"
      size="lg"
    >Add details</UiText>
    <UiText>Enter the details for this stage, then continue to review.</UiText>
    <UiInput
      label="Display name"
      value="Example workspace"
    />
  </UiStack>
</UiWizard>
import { Component } from "@angular/core";
import { UiButton, UiInput, UiStack, UiText, UiWizard } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiWizard, UiStack, UiText, UiInput, UiButton],
  template: `
    <div
      ui-wizard
      [value]="'details'"
      [label]="'Setup progress'"
      [steps]="[{ value: 'start', label: 'Start', complete: true }, { value: 'details', label: 'Details' }, { value: 'review', label: 'Review', disabled: true }, { value: 'finish', label: 'Finish', disabled: true }]"
    >
      <div
        ui-stack
        [gap]="'s'"
      >
        <ng-template
          ui-text
          [as]="'h2'"
          [size]="'lg'"
        >Add details</ng-template>
        <ng-template
          ui-text
        >Enter the details for this stage, then continue to review.</ng-template>
        <input
          ui-input
          label="Display name"
          [value]="'Example workspace'"
        >
      </div>
      <ng-container ngProjectAs="[slot='secondary']">
        <ng-template
          ui-button
        >Back</ng-template>
      </ng-container>
      <ng-container ngProjectAs="[slot='primary']">
        <ng-template
          ui-button
          [preset]="'primary'"
        >Continue</ng-template>
      </ng-container>
    </div>
  `,
})
export default class Example {}

Native destinations

Visited stages retain ordinary link navigation before JavaScript. Disabled future stages have no destination. Replace these example query destinations with your own routes.

Review your details before finishing.

Back

Code

<link
  rel="component"
  href="@threadlabs/looma/components/ui-wizard/ui-wizard.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-text/ui-text.html"
>
<link
  rel="component"
  href="@threadlabs/looma/components/ui-button/ui-button.html"
>

<ui-wizard
  value="review"
  label="Review progress"
  steps="[{&quot;value&quot;:&quot;details&quot;,&quot;label&quot;:&quot;Details&quot;,&quot;complete&quot;:true,&quot;href&quot;:&quot;?step=details&quot;},{&quot;value&quot;:&quot;review&quot;,&quot;label&quot;:&quot;Review&quot;,&quot;href&quot;:&quot;?step=review&quot;},{&quot;value&quot;:&quot;finish&quot;,&quot;label&quot;:&quot;Finish&quot;,&quot;disabled&quot;:true,&quot;href&quot;:&quot;?step=finish&quot;}]"
>
  <ui-text>Review your details before finishing.</ui-text>
  <ui-button
    slot="secondary"
    href="?step=details"
  >Back</ui-button>
  <ui-button
    slot="primary"
    preset="primary"
  >Finish</ui-button>
</ui-wizard>
<script setup>
  import { UiButton, UiText, UiWizard } from "@threadlabs/looma";
</script>

<template>
  <UiWizard
    value="review"
    label="Review progress"
    :steps="[{ value: 'details', label: 'Details', complete: true, href: '?step=details' }, { value: 'review', label: 'Review', href: '?step=review' }, { value: 'finish', label: 'Finish', disabled: true, href: '?step=finish' }]"
  >
    <UiText>Review your details before finishing.</UiText>
    <template #secondary>
      <UiButton
        href="?step=details"
      >Back</UiButton>
    </template>
    <template #primary>
      <UiButton
        preset="primary"
      >Finish</UiButton>
    </template>
  </UiWizard>
</template>
import { UiButton, UiText, UiWizard } from "@threadlabs/looma";

export default function Example() {
  return <UiWizard
    value="review"
    label="Review progress"
    steps={[{"value":"details","label":"Details","complete":true,"href":"?step=details"},{"value":"review","label":"Review","href":"?step=review"},{"value":"finish","label":"Finish","disabled":true,"href":"?step=finish"}]}
    slots={{
      secondary: <UiButton
        href="?step=details"
      >Back</UiButton>,
      primary: <UiButton
        preset="primary"
      >Finish</UiButton>,
    }}
  >
    <UiText>Review your details before finishing.</UiText>
  </UiWizard>;
}
/** @jsxImportSource preact */
import { UiButton, UiText, UiWizard } from "@threadlabs/looma";

export default function Example() {
  return <UiWizard
    value="review"
    label="Review progress"
    steps={[{"value":"details","label":"Details","complete":true,"href":"?step=details"},{"value":"review","label":"Review","href":"?step=review"},{"value":"finish","label":"Finish","disabled":true,"href":"?step=finish"}]}
    slots={{
      secondary: <UiButton
        href="?step=details"
      >Back</UiButton>,
      primary: <UiButton
        preset="primary"
      >Finish</UiButton>,
    }}
  >
    <UiText>Review your details before finishing.</UiText>
  </UiWizard>;
}
<script>
  import { UiButton, UiText, UiWizard } from "@threadlabs/looma";
</script>

{#snippet secondary()}
  <UiButton
    href="?step=details"
  >Back</UiButton>
{/snippet}

{#snippet primary()}
  <UiButton
    preset="primary"
  >Finish</UiButton>
{/snippet}

<UiWizard
  value="review"
  label="Review progress"
  steps={[{"value":"details","label":"Details","complete":true,"href":"?step=details"},{"value":"review","label":"Review","href":"?step=review"},{"value":"finish","label":"Finish","disabled":true,"href":"?step=finish"}]}
  slots={{ secondary, primary }}
>
  <UiText>Review your details before finishing.</UiText>
</UiWizard>
import { Component } from "@angular/core";
import { UiButton, UiText, UiWizard } from "@threadlabs/looma";

@Component({
  selector: "example-view",
  standalone: true,
  imports: [UiWizard, UiText, UiButton],
  template: `
    <div
      ui-wizard
      [value]="'review'"
      [label]="'Review progress'"
      [steps]="[{ value: 'details', label: 'Details', complete: true, href: '?step=details' }, { value: 'review', label: 'Review', href: '?step=review' }, { value: 'finish', label: 'Finish', disabled: true, href: '?step=finish' }]"
    >
      <ng-template
        ui-text
      >Review your details before finishing.</ng-template>
      <ng-container ngProjectAs="[slot='secondary']">
        <ng-template
          ui-button
          [href]="'?step=details'"
        >Back</ng-template>
      </ng-container>
      <ng-container ngProjectAs="[slot='primary']">
        <ng-template
          ui-button
          [preset]="'primary'"
        >Finish</ng-template>
      </ng-container>
    </div>
  `,
})
export default class Example {}

API

Package: @threadlabs/looma · Native root: div

Attributes

NameDescriptionPropertyTypeDefaultAllowed values
disabledDisables step navigation. The host controls availability of its slotted actions and content.disabledbooleanfalse—
labelAccessible name of the step navigation.labelstringProgress—
stepsStages in their reading order. Mark inaccessible stages disabled; availability and completion belong to the host.stepslist(object)
  • [].value: string (required)
  • [].label: string (required)
  • [].complete: boolean
  • [].disabled: boolean
  • [].href: string
[]—
valueCurrent stage value. Consumer controlled; navigation reports intent without changing it.valuestring—

Framework props

NameTypeDefaultAllowed values
disabledbooleanfalse—
labelstringProgress—
stepsreadonly ({ readonly value: string; readonly label: string; readonly complete?: boolean | null; readonly disabled?: boolean | null; readonly href?: string | null })[]
  • [].value: string (required)
  • [].label: string (required)
  • [].complete: boolean
  • [].disabled: boolean
  • [].href: string
[]—
valuestring—

Events

NameDetailDescription
navigateobject
  • value: string (required)
  • previousValue: string (required)
  • trigger: keyword (required)
Emitted with the declared detail payload.

Slots

NamePurpose
defaultDefault child content.
secondaryNamed content for the secondary region.
primaryNamed content for the primary region.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

No component tokens.

Shared tokens

TokenFallback
--ui-accent-solid—
--ui-border-strong—
--ui-border-width—
--ui-font-size-sm—
--ui-line-height-tight—
--ui-on-accent—
--ui-selection-border—
--ui-selection-surface—
--ui-selection-text—
--ui-space-2—
--ui-space-4—
--ui-text-primary—
--ui-text-secondary—