Modal Wizard

Multi-step workflow contained in a modal.

html

<div class="wizard-demo wizard-demo--backdrop">
  <div class="wizard wizard--standard wizard--step1" style="width:420px;--accent:#A8E6C1;" role="dialog" aria-modal="true" aria-labelledby="wizard-title">
    <div class="wizard__header">
      <h3 class="wizard__title" id="wizard-title">Create project</h3>
      <button class="wizard__close" type="button" aria-label="Close">
        <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round"/></svg>
      </button>
    </div>
    <p class="wizard__progress">Step 1 of 3</p>
    <div class="wizard__dots" aria-hidden="true">
      <span class="wizard__dot wizard__dot--1"></span>
      <span class="wizard__dot wizard__dot--2"></span>
      <span class="wizard__dot wizard__dot--3"></span>
    </div>
    <div class="wizard__step wizard__step--1">
      <h4>Project details</h4>
      <label class="wizard__field">Name<input class="wizard__input" type="text" value="Untitled project" readonly></label>
      <label class="wizard__field">Description<input class="wizard__input" type="text" value="A short summary" readonly></label>
    </div>
    <div class="wizard__step wizard__step--2">
      <h4>Team and permissions</h4>
      <label class="wizard__field">Team<input class="wizard__input" type="text" value="Design" readonly></label>
      <label class="wizard__field">Role<input class="wizard__input" type="text" value="Editor" readonly></label>
    </div>
    <div class="wizard__step wizard__step--3">
      <h4>Review and confirm</h4>
      <p class="wizard__summary">Untitled project will be created for the Design team with Editor access.</p>
    </div>
    <div class="wizard__actions">
      <button class="wizard__btn wizard__btn--ghost" type="button">Back</button>
      <button class="wizard__btn wizard__btn--solid" type="button">Next</button>
    </div>
  </div>
</div>
<style>
.wizard-demo { display: grid; place-items: center; min-height: 380px; border-radius: 12px; }
.wizard-demo--backdrop { background: rgba(36,20,48,.45); padding: 24px; }
.wizard { font: 16px/1.5 system-ui, sans-serif; background: #FAF7F2; color: #241430; border-radius: 14px; box-shadow: 0 20px 44px rgba(36,20,48,.28); padding: 24px; box-sizing: border-box; }
.wizard--compact { padding: 16px; }
.wizard__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.wizard__title { margin: 0; font-size: 18px; font-weight: 600; }
.wizard__close { background: none; border: none; cursor: pointer; color: #241430; padding: 4px; border-radius: 6px; }
.wizard__close:hover, .wizard__close:focus-visible { background: var(--accent); outline: none; }
.wizard__progress { margin: 0 0 10px; font-size: 13px; color: #24143099; }
.wizard__dots { display: flex; gap: 6px; margin-bottom: 18px; }
.wizard__dot { width: 8px; height: 8px; border-radius: 50%; background: #24143026; }
.wizard__step { display: none; }
.wizard__step h4 { margin: 0 0 12px; font-size: 15px; }
.wizard__field { display: block; font-size: 13px; font-weight: 500; margin-bottom: 12px; }
.wizard__input { display: block; width: 100%; box-sizing: border-box; margin-top: 4px; padding: 8px 10px; border-radius: 8px; border: 1.5px solid #24143033; font: 14px system-ui, sans-serif; color: #241430; background: #fff; }
.wizard__summary { font-size: 14px; color: #241430cc; margin: 0; }
.wizard__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 8px; }
.wizard__btn { font: 500 14px system-ui, sans-serif; padding: 9px 16px; border-radius: 8px; border: 1.5px solid transparent; cursor: pointer; }
.wizard__btn--ghost { background: transparent; border-color: #24143033; color: #241430; }
.wizard__btn--solid { background: var(--accent); border-color: var(--accent); color: #241430; }
.wizard__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wizard--step1 .wizard__step--1 { display: block; }
.wizard--step1 .wizard__dot--1 { background: var(--accent); }
.wizard--step2 .wizard__step--2 { display: block; }
.wizard--step2 .wizard__dot--1, .wizard--step2 .wizard__dot--2 { background: var(--accent); }
.wizard--step3 .wizard__step--3 { display: block; }
.wizard--step3 .wizard__dot--1, .wizard--step3 .wizard__dot--2, .wizard--step3 .wizard__dot--3 { background: var(--accent); }
</style>

tailwind

<div class="grid place-items-center min-h-[380px] rounded-xl bg-[#241430]/45 p-6">
  <div role="dialog" aria-modal="true" aria-labelledby="wizard-tw-title" class="wizard-tw wizard-tw--standard wizard-tw--step1 bg-[#FAF7F2] text-[#241430] rounded-2xl shadow-2xl p-6 box-border font-sans" style="width:420px;--accent:#A8E6C1;">
    <div class="flex items-center justify-between mb-1.5">
      <h3 id="wizard-tw-title" class="m-0 text-lg font-semibold">Create project</h3>
      <button type="button" aria-label="Close" class="bg-transparent border-none cursor-pointer text-[#241430] p-1 rounded-md hover:bg-[var(--accent)] focus-visible:bg-[var(--accent)] focus-visible:outline-none">
        <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round"/></svg>
      </button>
    </div>
    <p class="m-0 mb-2.5 text-[13px] text-[#241430]/60">Step 1 of 3</p>
    <div class="flex gap-1.5 mb-[18px]" aria-hidden="true">
      <span class="wizard-tw__dot wizard-tw__dot--1 w-2 h-2 rounded-full bg-[#24143026]"></span>
      <span class="wizard-tw__dot wizard-tw__dot--2 w-2 h-2 rounded-full bg-[#24143026]"></span>
      <span class="wizard-tw__dot wizard-tw__dot--3 w-2 h-2 rounded-full bg-[#24143026]"></span>
    </div>
    <div class="wizard-tw__step wizard-tw__step--1 hidden">
      <h4 class="mt-0 mb-3 text-[15px]">Project details</h4>
      <label class="block text-[13px] font-medium mb-3">Name<input type="text" value="Untitled project" readonly class="block w-full box-border mt-1 px-2.5 py-2 rounded-lg border border-[#24143033] text-sm bg-white"></label>
      <label class="block text-[13px] font-medium mb-3">Description<input type="text" value="A short summary" readonly class="block w-full box-border mt-1 px-2.5 py-2 rounded-lg border border-[#24143033] text-sm bg-white"></label>
    </div>
    <div class="wizard-tw__step wizard-tw__step--2 hidden">
      <h4 class="mt-0 mb-3 text-[15px]">Team and permissions</h4>
      <label class="block text-[13px] font-medium mb-3">Team<input type="text" value="Design" readonly class="block w-full box-border mt-1 px-2.5 py-2 rounded-lg border border-[#24143033] text-sm bg-white"></label>
      <label class="block text-[13px] font-medium mb-3">Role<input type="text" value="Editor" readonly class="block w-full box-border mt-1 px-2.5 py-2 rounded-lg border border-[#24143033] text-sm bg-white"></label>
    </div>
    <div class="wizard-tw__step wizard-tw__step--3 hidden">
      <h4 class="mt-0 mb-3 text-[15px]">Review and confirm</h4>
      <p class="text-sm text-[#241430]/80 m-0">Untitled project will be created for the Design team with Editor access.</p>
    </div>
    <div class="flex justify-end gap-2.5 mt-2">
      <button type="button" class="px-4 py-2 rounded-lg text-sm font-medium border border-[#24143033]">Back</button>
      <button type="button" class="px-4 py-2 rounded-lg text-sm font-medium" style="background:#A8E6C1;border:1.5px solid #A8E6C1;">Next</button>
    </div>
  </div>
</div>
<style>
.wizard-tw--compact { padding: 16px !important; }
.wizard-tw--step1 .wizard-tw__step--1 { display: block; }
.wizard-tw--step1 .wizard-tw__dot--1 { background: var(--accent); }
.wizard-tw--step2 .wizard-tw__step--2 { display: block; }
.wizard-tw--step2 .wizard-tw__dot--1, .wizard-tw--step2 .wizard-tw__dot--2 { background: var(--accent); }
.wizard-tw--step3 .wizard-tw__step--3 { display: block; }
.wizard-tw--step3 .wizard-tw__dot--1, .wizard-tw--step3 .wizard-tw__dot--2, .wizard-tw--step3 .wizard-tw__dot--3 { background: var(--accent); }
</style>

react

export function ModalWizard({
  title = "Create project",
  step = 1,
  variant = "standard",
  accent = "#A8E6C1",
  width = 420,
  backdrop = true,
}) {
  const padding = variant === "compact" ? 16 : 24;
  const fieldStyle = { display: "block", fontSize: 13, fontWeight: 500, marginBottom: 12 };
  const inputStyle = { display: "block", width: "100%", boxSizing: "border-box", marginTop: 4,
    padding: "8px 10px", borderRadius: 8, border: "1.5px solid #24143033", fontSize: 14, background: "#fff", color: "#241430" };
  const stepContent = {
    1: (
      <>
        <h4 style={{ margin: "0 0 12px", fontSize: 15 }}>Project details</h4>
        <label style={fieldStyle}>Name<input type="text" defaultValue="Untitled project" readOnly style={inputStyle} /></label>
        <label style={fieldStyle}>Description<input type="text" defaultValue="A short summary" readOnly style={inputStyle} /></label>
      </>
    ),
    2: (
      <>
        <h4 style={{ margin: "0 0 12px", fontSize: 15 }}>Team and permissions</h4>
        <label style={fieldStyle}>Team<input type="text" defaultValue="Design" readOnly style={inputStyle} /></label>
        <label style={fieldStyle}>Role<input type="text" defaultValue="Editor" readOnly style={inputStyle} /></label>
      </>
    ),
    3: (
      <>
        <h4 style={{ margin: "0 0 12px", fontSize: 15 }}>Review and confirm</h4>
        <p style={{ fontSize: 14, color: "#241430cc", margin: 0 }}>Untitled project will be created for the Design team with Editor access.</p>
      </>
    ),
  }[step] ?? null;

  return (
    <div style={{ display: "grid", placeItems: "center", minHeight: 380, borderRadius: 12,
      background: backdrop ? "rgba(36,20,48,.45)" : "transparent", padding: backdrop ? 24 : 0 }}>
      <div role="dialog" aria-modal={backdrop ? "true" : "false"} aria-labelledby="wizard-title" style={{
        width, boxSizing: "border-box", background: "#FAF7F2", color: "#241430", borderRadius: 14,
        boxShadow: "0 20px 44px rgba(36,20,48,.28)", padding, fontFamily: "system-ui, sans-serif" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 6 }}>
          <h3 id="wizard-title" style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>{title}</h3>
          <button type="button" aria-label="Close" style={{ background: "none", border: "none", cursor: "pointer",
            color: "#241430", padding: 4, borderRadius: 6 }}>
            <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" strokeWidth="1.6" fill="none" strokeLinecap="round"/></svg>
          </button>
        </div>
        <p style={{ margin: "0 0 10px", fontSize: 13, color: "#24143099" }}>Step {step} of 3</p>
        <div style={{ display: "flex", gap: 6, marginBottom: 18 }} aria-hidden="true">
          <span style={{ width: 8, height: 8, borderRadius: "50%", background: step >= 1 ? accent : "#24143026" }} />
          <span style={{ width: 8, height: 8, borderRadius: "50%", background: step >= 2 ? accent : "#24143026" }} />
          <span style={{ width: 8, height: 8, borderRadius: "50%", background: step >= 3 ? accent : "#24143026" }} />
        </div>
        {stepContent}
        <div style={{ display: "flex", justifyContent: "flex-end", gap: 10, marginTop: 8 }}>
          <button type="button" style={{ padding: "9px 16px", borderRadius: 8, border: "1.5px solid #24143033",
            background: "transparent", color: "#241430", fontSize: 14, fontWeight: 500 }}>Back</button>
          <button type="button" style={{ padding: "9px 16px", borderRadius: 8, border: `1.5px solid ${accent}`,
            background: accent, color: "#241430", fontSize: 14, fontWeight: 500 }}>{step >= 3 ? "Finish" : "Next"}</button>
        </div>
      </div>
    </div>
  );
}