Onboarding Checklist Rails Component

A compact setup guide that turns progress into momentum. Feed it a JSON object, show completed and loading states automatically, and choose whether the expanded checklist overlays the page or joins the document flow.

Installation

1. Stimulus Controller Setup

The controller renders task states from JSON and manages each expansion mode. Tailwind handles the visual transitions, with the browser’s native Web Animations API reserved for measured heights and dynamic completion particles.

This code is available to Pro users only.

// Hey curious person!
// You seem to be sneaking around the code...
// I hope you're enjoying the components!
// Have a great day!

class SecretMessage {
  constructor() {
    this.message = "Thanks for checking out Rails Blocks!";
    this.compliment = "You're awesome!";
  }

  reveal() {
    console.log(this.message);
    return this.compliment;
  }
}

const secret = new SecretMessage();
secret.reveal();

2. Floating UI Installation

Overlay modes use the same portaled positioning foundation as the dropdown and popover components.

pin "@floating-ui/dom", to: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.6/+esm"
Terminal
npm install @floating-ui/dom
Terminal
yarn add @floating-ui/dom

3. Number Flow Installation

The progress percentage uses Number Flow for smooth, direction-aware digit transitions.

pin "number-flow", to: "https://esm.sh/number-flow"
pin "number-flow/group", to: "https://esm.sh/number-flow/group"
Terminal
npm install number-flow
Terminal
yarn add number-flow

Examples

Overlay below

Opens the checklist beneath its trigger without shifting the surrounding layout.

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

Overlay above

Opens the checklist above its trigger when the component sits near the bottom of a page or viewport.

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

Inline flow

Expands the checklist in the document flow and moves the content below it.

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

JSON input

Pass one JSON object through data-onboarding-checklist-data-value. Build task statuses from your Rails records so a refresh always restores the real state. Supported states are pending, loading, completed, and skipped.

When url is present, the entire task row becomes a link and the action appears as its destination label. Add target: "_blank" only when the destination should open separately. Navigating does not complete the task automatically—send the updated status from your real activation event so checklist progress reflects value reached, not clicks.

Set data-onboarding-checklist-mode-value to overlay-above or overlay-below for a portaled Floating UI panel with no layout shift or clipping, or inline for an animated expansion that moves the content below it. The legacy overlay value remains an alias for overlay-above.

Checklist options

  • checklist_id scopes live updates when more than one checklist is present.
  • title, panel_title, panel_description, and completed_summary control the copy.
  • update_event changes the default onboarding-checklist:update event name.
  • completed_event changes the emitted onboarding-checklist:completed event.
  • dismiss_on_complete removes the checklist after its success animation. Tune it with dismiss_delay and dismiss_duration.

Task options

  • id, title, description, status, and action define the task.
  • url and target make the row a link; focus makes it reveal and focus an element already on the page.
  • action_event makes the row dispatch an app-defined event for modals, drawers, or any custom workflow. Add close_on_action: true when the checklist should collapse first.
  • completion_event and completion_selector complete a task from real app behavior. Optional completion_url, completion_method, and completion_payload persist it.
  • optional: true adds Skip/Restore controls. Skipped tasks leave the progress denominator; use skip_url, skip_method, and skip_payload to persist that choice.
  • icon accepts user, folder, users, sparkles, bookmark, bell, link, keyboard, or chrome. For your own SVG, pass icon_path or icon_paths, plus optional icon_view_box and icon_fill.

Real app integration

Derive initial state in Rails

Keep the server authoritative. A helper or presenter can build the JSON from your records and return nothing once every required task is complete, preventing a completed checklist from flashing back on refresh.

checklist = {
  checklist_id: "account-setup",
  dismiss_on_complete: true,
  tasks: [
    {
      id: "profile",
      title: "Complete your profile",
      status: current_user.profile_complete? ? "completed" : "pending",
      action: "Open",
      url: edit_profile_path,
      icon: "user"
    },
    {
      id: "invite",
      title: "Invite a teammate",
      status: current_account.members.many? ? "completed" : "pending",
      action: "Invite",
      action_event: "team:invite",
      icon: "users"
    }
  ]
}

required_tasks = checklist[:tasks].reject { |task| task[:status] == "skipped" }
checklist = nil if required_tasks.any? && required_tasks.all? { |task| task[:status] == "completed" }

Update tasks after Turbo or in-page actions

Dispatch one generic event after the app reaches the milestone. Updates merge by task ID, so the server can change status, copy, URL, or action without replacing the whole checklist. Pass replace: true with a full tasks array when replacing all task data is simpler.

document.dispatchEvent(new CustomEvent("onboarding-checklist:update", {
  detail: {
    checklist_id: "account-setup",
    task: {
      id: "invite",
      status: "completed",
      action: "Manage",
      url: "/account/members"
    }
  }
}))

Connect custom task actions

Use action_event when a task should open an app-owned modal or start a workflow instead of navigating. The event bubbles from the checklist and includes both camelCase and snake_case task IDs.

<div
  data-controller="onboarding-checklist invite-modal"
  data-action="team:invite->invite-modal#open"
  data-onboarding-checklist-data-value="..."
>
  ...
</div>

Table of contents

Powered by

Get notified when new components come out