Workflow

Defined sequence of states/actions.

Order fulfilment

  1. Received

    Order confirmed and queued

    Completed Current Upcoming
  2. Packing

    Items picked and boxed

    Completed Current Upcoming
  3. Shipped

    Handed to courier

    Completed Current Upcoming
  4. Delivered

    Received by customer

    Completed Current Upcoming

html

<div class="workflow workflow--horizontal" data-current="packing" style="--accent:#A8E6C1;">
  <h3 class="workflow__title">Order fulfilment</h3>
  <ol class="workflow__stages">
    <li class="workflow__stage workflow__stage--received">
      <span class="workflow__dot" aria-hidden="true"></span>
      <div class="workflow__stage-body">
        <p class="workflow__stage-name">Received</p>
        <p class="workflow__stage-desc">Order confirmed and queued</p>
        <span class="workflow__badge workflow__badge--completed">Completed</span>
        <span class="workflow__badge workflow__badge--current" aria-current="step">Current</span>
        <span class="workflow__badge workflow__badge--upcoming">Upcoming</span>
      </div>
    </li>
    <li class="workflow__stage workflow__stage--packing">
      <span class="workflow__dot" aria-hidden="true"></span>
      <div class="workflow__stage-body">
        <p class="workflow__stage-name">Packing</p>
        <p class="workflow__stage-desc">Items picked and boxed</p>
        <span class="workflow__badge workflow__badge--completed">Completed</span>
        <span class="workflow__badge workflow__badge--current" aria-current="step">Current</span>
        <span class="workflow__badge workflow__badge--upcoming">Upcoming</span>
      </div>
    </li>
    <li class="workflow__stage workflow__stage--shipped">
      <span class="workflow__dot" aria-hidden="true"></span>
      <div class="workflow__stage-body">
        <p class="workflow__stage-name">Shipped</p>
        <p class="workflow__stage-desc">Handed to courier</p>
        <span class="workflow__badge workflow__badge--completed">Completed</span>
        <span class="workflow__badge workflow__badge--current" aria-current="step">Current</span>
        <span class="workflow__badge workflow__badge--upcoming">Upcoming</span>
      </div>
    </li>
    <li class="workflow__stage workflow__stage--delivered">
      <span class="workflow__dot" aria-hidden="true"></span>
      <div class="workflow__stage-body">
        <p class="workflow__stage-name">Delivered</p>
        <p class="workflow__stage-desc">Received by customer</p>
        <span class="workflow__badge workflow__badge--completed">Completed</span>
        <span class="workflow__badge workflow__badge--current" aria-current="step">Current</span>
        <span class="workflow__badge workflow__badge--upcoming">Upcoming</span>
      </div>
    </li>
  </ol>
</div>
<style>
.workflow { width: 640px; max-width: 100%; padding: 20px; background: #FAF7F2; color: #241430; font: 14px/1.4 system-ui, sans-serif; border-radius: 14px; box-shadow: 0 8px 24px rgba(36,20,48,.12); box-sizing: border-box; }
.workflow__title { margin: 0 0 16px; font-size: 16px; }
.workflow__stages { list-style: none; margin: 0; padding: 0; display: flex; gap: 18px; }
.workflow--vertical .workflow__stages { flex-direction: column; gap: 14px; }
.workflow__stage { display: flex; gap: 10px; flex: 1; position: relative; }
.workflow--vertical .workflow__stage { flex: none; }
.workflow__dot { width: 10px; height: 10px; border-radius: 50%; background: #24143022; margin-top: 4px; flex-shrink: 0; }
.workflow__stage-name { margin: 0; font-weight: 700; font-size: 13.5px; }
.workflow__stage-desc { margin: 2px 0 6px; font-size: 12px; color: #241430b3; }
.workflow__badge { display: none; font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; text-transform: uppercase; letter-spacing: .03em; }
.workflow__badge--completed { display: inline-flex; background: #24143014; color: #241430b3; }
.workflow[data-current="received"] .workflow__stage--received .workflow__dot,
.workflow[data-current="packing"] .workflow__stage--packing .workflow__dot,
.workflow[data-current="shipped"] .workflow__stage--shipped .workflow__dot,
.workflow[data-current="delivered"] .workflow__stage--delivered .workflow__dot { background: var(--accent); }
.workflow[data-current="received"] .workflow__stage--received .workflow__badge--completed,
.workflow[data-current="packing"] .workflow__stage--packing .workflow__badge--completed,
.workflow[data-current="shipped"] .workflow__stage--shipped .workflow__badge--completed,
.workflow[data-current="delivered"] .workflow__stage--delivered .workflow__badge--completed { display: none; }
.workflow[data-current="received"] .workflow__stage--received .workflow__badge--current,
.workflow[data-current="packing"] .workflow__stage--packing .workflow__badge--current,
.workflow[data-current="shipped"] .workflow__stage--shipped .workflow__badge--current,
.workflow[data-current="delivered"] .workflow__stage--delivered .workflow__badge--current { display: inline-flex; background: var(--accent); color: #17532F; }
.workflow[data-current="received"] .workflow__stage--received ~ .workflow__stage .workflow__badge--completed,
.workflow[data-current="packing"] .workflow__stage--packing ~ .workflow__stage .workflow__badge--completed,
.workflow[data-current="shipped"] .workflow__stage--shipped ~ .workflow__stage .workflow__badge--completed { display: none; }
.workflow[data-current="received"] .workflow__stage--received ~ .workflow__stage .workflow__badge--upcoming,
.workflow[data-current="packing"] .workflow__stage--packing ~ .workflow__stage .workflow__badge--upcoming,
.workflow[data-current="shipped"] .workflow__stage--shipped ~ .workflow__stage .workflow__badge--upcoming { display: inline-flex; color: #241430b3; }
.workflow[data-current="received"] .workflow__stage--received ~ .workflow__stage .workflow__dot,
.workflow[data-current="packing"] .workflow__stage--packing ~ .workflow__stage .workflow__dot,
.workflow[data-current="shipped"] .workflow__stage--shipped ~ .workflow__stage .workflow__dot { background: #24143022; }
</style>

tailwind

<div class="workflow-tw workflow-tw--horizontal w-[640px] max-w-full p-5 bg-[#FAF7F2] text-[#241430] font-sans text-sm rounded-2xl shadow-lg box-border" data-current="packing" style="--accent:#A8E6C1;">
  <h3 class="m-0 mb-4 text-base">Order fulfilment</h3>
  <ol class="workflow-tw__stages list-none m-0 p-0 flex gap-4">
    <li class="workflow-tw__stage workflow-tw__stage--received flex gap-2.5 relative flex-1">
      <span class="workflow-tw__dot w-2.5 h-2.5 rounded-full mt-1 flex-shrink-0" style="background:#24143022;"></span>
      <div>
        <p class="m-0 font-bold text-[13.5px]">Received</p>
        <p class="mt-0.5 mb-1.5 text-xs text-[#241430]/70">Order confirmed and queued</p>
        <span class="workflow-tw__badge workflow-tw__badge--completed inline-flex text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide bg-[#24143014] text-[#241430]/70">Completed</span>
        <span class="workflow-tw__badge workflow-tw__badge--current text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide" aria-current="step">Current</span>
        <span class="workflow-tw__badge workflow-tw__badge--upcoming text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide">Upcoming</span>
      </div>
    </li>
    <li class="workflow-tw__stage workflow-tw__stage--packing flex gap-2.5 relative flex-1">
      <span class="workflow-tw__dot w-2.5 h-2.5 rounded-full mt-1 flex-shrink-0" style="background:#24143022;"></span>
      <div>
        <p class="m-0 font-bold text-[13.5px]">Packing</p>
        <p class="mt-0.5 mb-1.5 text-xs text-[#241430]/70">Items picked and boxed</p>
        <span class="workflow-tw__badge workflow-tw__badge--completed inline-flex text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide bg-[#24143014] text-[#241430]/70">Completed</span>
        <span class="workflow-tw__badge workflow-tw__badge--current text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide" aria-current="step">Current</span>
        <span class="workflow-tw__badge workflow-tw__badge--upcoming text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide">Upcoming</span>
      </div>
    </li>
    <li class="workflow-tw__stage workflow-tw__stage--shipped flex gap-2.5 relative flex-1">
      <span class="workflow-tw__dot w-2.5 h-2.5 rounded-full mt-1 flex-shrink-0" style="background:#24143022;"></span>
      <div>
        <p class="m-0 font-bold text-[13.5px]">Shipped</p>
        <p class="mt-0.5 mb-1.5 text-xs text-[#241430]/70">Handed to courier</p>
        <span class="workflow-tw__badge workflow-tw__badge--completed inline-flex text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide bg-[#24143014] text-[#241430]/70">Completed</span>
        <span class="workflow-tw__badge workflow-tw__badge--current text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide" aria-current="step">Current</span>
        <span class="workflow-tw__badge workflow-tw__badge--upcoming text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide">Upcoming</span>
      </div>
    </li>
    <li class="workflow-tw__stage workflow-tw__stage--delivered flex gap-2.5 relative flex-1">
      <span class="workflow-tw__dot w-2.5 h-2.5 rounded-full mt-1 flex-shrink-0" style="background:#24143022;"></span>
      <div>
        <p class="m-0 font-bold text-[13.5px]">Delivered</p>
        <p class="mt-0.5 mb-1.5 text-xs text-[#241430]/70">Received by customer</p>
        <span class="workflow-tw__badge workflow-tw__badge--completed inline-flex text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide bg-[#24143014] text-[#241430]/70">Completed</span>
        <span class="workflow-tw__badge workflow-tw__badge--current text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide" aria-current="step">Current</span>
        <span class="workflow-tw__badge workflow-tw__badge--upcoming text-[11px] font-bold px-2 py-px rounded-full uppercase tracking-wide">Upcoming</span>
      </div>
    </li>
  </ol>
</div>
<style>
.workflow-tw--vertical .workflow-tw__stages { flex-direction: column; }
.workflow-tw--vertical .workflow-tw__stage { flex: none; }
.workflow-tw__badge--current, .workflow-tw__badge--upcoming { display: none; }
.workflow-tw[data-current="received"] .workflow-tw__stage--received .workflow-tw__dot,
.workflow-tw[data-current="packing"] .workflow-tw__stage--packing .workflow-tw__dot,
.workflow-tw[data-current="shipped"] .workflow-tw__stage--shipped .workflow-tw__dot,
.workflow-tw[data-current="delivered"] .workflow-tw__stage--delivered .workflow-tw__dot { background: var(--accent) !important; }
.workflow-tw[data-current="received"] .workflow-tw__stage--received .workflow-tw__badge--completed,
.workflow-tw[data-current="packing"] .workflow-tw__stage--packing .workflow-tw__badge--completed,
.workflow-tw[data-current="shipped"] .workflow-tw__stage--shipped .workflow-tw__badge--completed,
.workflow-tw[data-current="delivered"] .workflow-tw__stage--delivered .workflow-tw__badge--completed { display: none; }
.workflow-tw[data-current="received"] .workflow-tw__stage--received .workflow-tw__badge--current,
.workflow-tw[data-current="packing"] .workflow-tw__stage--packing .workflow-tw__badge--current,
.workflow-tw[data-current="shipped"] .workflow-tw__stage--shipped .workflow-tw__badge--current,
.workflow-tw[data-current="delivered"] .workflow-tw__stage--delivered .workflow-tw__badge--current { display: inline-flex; background: var(--accent); color: #17532F; }
.workflow-tw[data-current="received"] .workflow-tw__stage--received ~ .workflow-tw__stage .workflow-tw__badge--completed,
.workflow-tw[data-current="packing"] .workflow-tw__stage--packing ~ .workflow-tw__stage .workflow-tw__badge--completed,
.workflow-tw[data-current="shipped"] .workflow-tw__stage--shipped ~ .workflow-tw__stage .workflow-tw__badge--completed { display: none; }
.workflow-tw[data-current="received"] .workflow-tw__stage--received ~ .workflow-tw__stage .workflow-tw__badge--upcoming,
.workflow-tw[data-current="packing"] .workflow-tw__stage--packing ~ .workflow-tw__stage .workflow-tw__badge--upcoming,
.workflow-tw[data-current="shipped"] .workflow-tw__stage--shipped ~ .workflow-tw__stage .workflow-tw__badge--upcoming { display: inline-flex; color: #241430b3; }
</style>

react

export function Workflow({
  title = "Order fulfilment",
  current = "packing",
  orientation = "horizontal",
  accent = "#A8E6C1",
  showDescription = true,
}) {
  const stages = [
    { key: "received", name: "Received", desc: "Order confirmed and queued" },
    { key: "packing", name: "Packing", desc: "Items picked and boxed" },
    { key: "shipped", name: "Shipped", desc: "Handed to courier" },
    { key: "delivered", name: "Delivered", desc: "Received by customer" },
  ];
  const currentIndex = stages.findIndex((s) => s.key === current);
  return (
    <div style={{ width: 640, maxWidth: "100%", padding: 20, background: "#FAF7F2", color: "#241430",
      fontFamily: "system-ui, sans-serif", fontSize: 14, borderRadius: 14, boxShadow: "0 8px 24px rgba(36,20,48,.12)",
      boxSizing: "border-box" }}>
      <h3 style={{ margin: "0 0 16px", fontSize: 16 }}>{title}</h3>
      <ol style={{ listStyle: "none", margin: 0, padding: 0, display: "flex",
        flexDirection: orientation === "vertical" ? "column" : "row", gap: orientation === "vertical" ? 14 : 18 }}>
        {stages.map((stage, i) => {
          const status = i < currentIndex ? "completed" : i === currentIndex ? "current" : "upcoming";
          return (
            <li key={stage.key} style={{ display: "flex", gap: 10, flex: orientation === "vertical" ? "none" : 1 }}>
              <span aria-hidden="true" style={{ width: 10, height: 10, borderRadius: "50%", marginTop: 4, flexShrink: 0,
                background: status === "upcoming" ? "#24143022" : accent }} />
              <div>
                <p style={{ margin: 0, fontWeight: 700, fontSize: 13.5 }}>{stage.name}</p>
                {showDescription && <p style={{ margin: "2px 0 6px", fontSize: 12, color: "#241430b3" }}>{stage.desc}</p>}
                <span aria-current={status === "current" ? "step" : undefined} style={{ fontSize: 11, fontWeight: 700,
                  padding: "1px 8px", borderRadius: 999, textTransform: "uppercase", letterSpacing: "0.03em",
                  background: status === "current" ? accent : status === "completed" ? "#24143014" : "transparent",
                  color: status === "current" ? "#17532F" : "#241430b3" }}>
                  {status}
                </span>
              </div>
            </li>
          );
        })}
      </ol>
    </div>
  );
}