1 GitHub star = 1 new component.

Star the repo
Features & Grids

How It Works Steps

Numbered process steps connected by a line, horizontal on desktop and vertical on mobile.

processstepshow-it-worksonboarding
Live Preview

How it works

Ship a polished page in four steps

  1. Step 1: Pick sections

    Browse heroes, features, pricing and more in the catalog.

    2 min
  2. Step 2: Copy the code

    Paste components into your Astro project. No install needed.

    1 min
  3. Step 3: Customize props

    Change copy, colors and data through typed props and tokens.

    10 min
  4. Step 4: Deploy

    Build static HTML and ship to any CDN or edge host.

    30 s

Usage

Copy and paste this code into your React component:

tsx
import { HowItWorksSteps } from 'react-component-kit-ui/sections';

<HowItWorksSteps title="Ship in three steps" steps={[
    { title: 'Pick', text: 'Choose sections.', time: '2 min' },
    { title: 'Paste', text: 'Copy into your project.' },
    { title: 'Deploy', text: 'Push to any CDN.' },
  ]} />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/sections/HowItWorksSteps.tsx + HowItWorksSteps.css:

HowItWorksSteps.tsx
'use client';

import type { CSSProperties } from 'react';
import './HowItWorksSteps.css';

/** "a: b; --c: d" โ†’ style object */
function st(css: string | undefined): CSSProperties {
  const o: Record<string, string> = {};
  for (const decl of String(css ?? '').split(';')) {
    const i = decl.indexOf(':');
    if (i < 0) continue;
    const k = decl.slice(0, i).trim();
    if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
  }
  return o as CSSProperties;
}

interface Step { title: string; text: string; time?: string }

/**
 * HowItWorksSteps โ€” Numbered process section ("How it works") with a connecting
 * line between steps. Horizontal on desktop, vertical on mobile.
 *
 * @param {string} eyebrow - Small label above the heading.
 * @param {string} title - Section heading.
 * @param {Array<{title: string, text: string, time?: string}>} steps - Ordered steps.
 * @param {{label: string, href: string}} cta - Optional CTA under the steps.
 * @param {string} variant - Step marker style (numbers|dots|cards).
 * @param {string} size - Vertical rhythm (sm|md|lg).
 * @param {string} className - Extra CSS class.
 */
export interface HowItWorksStepsProps {
  eyebrow?: string;
  title?: string;
  steps?: Step[];
  cta?: { label: string; href: string };
  variant?: 'numbers' | 'dots' | 'cards';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function HowItWorksSteps({
  eyebrow = 'How it works',
  title = 'Ship a polished page in four steps',
  steps = [
    { title: 'Pick sections', text: 'Browse heroes, features, pricing and more in the catalog.', time: '2 min' },
    { title: 'Copy the code', text: 'Paste components into your Astro project. No install needed.', time: '1 min' },
    { title: 'Customize props', text: 'Change copy, colors and data through typed props and tokens.', time: '10 min' },
    { title: 'Deploy', text: 'Build static HTML and ship to any CDN or edge host.', time: '30 s' },
  ],
  cta = { label: 'Start building', href: '#' },
  variant = 'numbers',
  size = 'md',
  className = '',
}: HowItWorksStepsProps) {
  const id = `hiw-${Math.random().toString(36).slice(2, 8)}`;
  return (
    <>
      <div className="cq-how-it-works-steps-cq" data-rk-1a3d09="">
      <section className={`hiw hiw--${variant} hiw--${size} ${className}`} aria-labelledby={id} style={st(`--hiw-n:${steps.length}`)} data-rk-1a3d09="">
        <div className="hiw__inner" data-rk-1a3d09="">
          <header className="hiw__head" data-rk-1a3d09="">
            <p className="hiw__eyebrow" data-rk-1a3d09="">{eyebrow}</p>
            <h2 id={id} className="hiw__title" data-rk-1a3d09="">{title}</h2>
          </header>
          <ol className="hiw__steps" data-rk-1a3d09="">
            {steps.map((s, i) => (
              <li key={i} className="hiw__step" style={st(`--i:${i}`)} data-rk-1a3d09="">
                <span className="hiw__marker" aria-hidden="true" data-rk-1a3d09="">{variant === 'dots' ? '' : String(i + 1).padStart(2, '0')}</span>
                <div className="hiw__body" data-rk-1a3d09="">
                  <h3 className="hiw__step-title" data-rk-1a3d09=""><span className="hiw__sr" data-rk-1a3d09="">Step {i + 1}: </span>{s.title}</h3>
                  <p className="hiw__text" data-rk-1a3d09="">{s.text}</p>
                  {s.time && (
                    <span className="hiw__time" data-rk-1a3d09="">
                      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true" data-rk-1a3d09=""><circle cx="12" cy="12" r="10" data-rk-1a3d09="" /><path d="M12 6v6l4 2" data-rk-1a3d09="" /></svg>
                      {s.time}
                    </span>
                  )}
                </div>
              </li>
            ))}
          </ol>
          {cta && (
            <div className="hiw__cta" data-rk-1a3d09="">
              <a href={cta.href} data-rk-1a3d09="">{cta.label}</a>
            </div>
          )}
        </div>
      </section>
      </div>
    </>
  );
}

export default HowItWorksSteps;
HowItWorksSteps.css
/* HowItWorksSteps โ€” ported from astro-component-kit (sections/HowItWorksSteps.astro) */

.cq-how-it-works-steps-cq:where([data-rk-1a3d09]) { container-type: inline-size; }
.hiw:where([data-rk-1a3d09]) {
  --hiw-py: 5.5rem;
  padding: var(--hiw-py) var(--sp-6, 1.5rem);
  background: var(--c-bg, #050505);
}
.hiw--sm:where([data-rk-1a3d09]) { --hiw-py: 3.5rem; }
.hiw--lg:where([data-rk-1a3d09]) { --hiw-py: 7.5rem; }
.hiw__inner:where([data-rk-1a3d09]) { max-width: 1180px; margin: 0 auto; }
.hiw__head:where([data-rk-1a3d09]) { text-align: center; margin-bottom: 3.5rem; }
.hiw__eyebrow:where([data-rk-1a3d09]) { margin: 0 0 0.75rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-primary-light, #d4a017); }
.hiw__title:where([data-rk-1a3d09]) { margin: 0; font-size: clamp(1.9rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); text-wrap: balance; }
.hiw__steps:where([data-rk-1a3d09]) {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(var(--hiw-n), minmax(0, 1fr));
  gap: 2rem;
}
.hiw__steps:where([data-rk-1a3d09])::before {
  content: '';
  position: absolute;
  top: 24px;
  left: calc(100% / var(--hiw-n) / 2);
  right: calc(100% / var(--hiw-n) / 2);
  height: 1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--c-primary-light, #d4a017) 60%, transparent), rgb(var(--ink-rgb, 255 255 255) / 0.1));
}
.hiw--cards:where([data-rk-1a3d09]) .hiw__steps:where([data-rk-1a3d09])::before { display: none; }
.hiw__step:where([data-rk-1a3d09]) {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  animation: how-it-works-steps-hiw-in 0.6s ease both;
  animation-delay: calc(var(--i) * 120ms);
}
.hiw--cards:where([data-rk-1a3d09]) .hiw__step:where([data-rk-1a3d09]) {
  align-items: flex-start;
  text-align: left;
  padding: 1.5rem;
  border-radius: var(--r-lg, 20px);
  background: rgb(var(--ink-rgb, 255 255 255) / 0.025);
  border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07));
}
@keyframes how-it-works-steps-hiw-in { from { opacity: 0; transform: translateY(10px); } }
.hiw__marker:where([data-rk-1a3d09]) {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: 1.25rem;
  border-radius: 50%;
  background: var(--c-bg-1, #0a0a0a);
  border: 1px solid color-mix(in srgb, var(--c-primary-light, #d4a017) 45%, transparent);
  color: var(--c-primary-light, #d4a017);
  font-family: var(--font-mono, monospace);
  font-size: 0.875rem;
  font-weight: 700;
  box-shadow: 0 0 0 6px var(--c-bg, #050505);
}
.hiw--dots:where([data-rk-1a3d09]) .hiw__marker:where([data-rk-1a3d09]) { width: 16px; height: 16px; margin: 16px 0 1.5rem; background: var(--c-primary-light, #d4a017); }
.hiw__step-title:where([data-rk-1a3d09]) { margin: 0 0 0.5rem; font-size: 1.125rem; font-weight: 650; color: var(--c-text-1, #fff); }
.hiw__text:where([data-rk-1a3d09]) { margin: 0; font-size: 0.9375rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
.hiw__time:where([data-rk-1a3d09]) {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.85rem;
  padding: 0.2rem 0.6rem;
  border-radius: var(--r-full, 999px);
  background: rgb(var(--ink-rgb, 255 255 255) / 0.05);
  font-size: 0.75rem;
  color: var(--c-text-2, #a3a3a3);
}
.hiw__sr:where([data-rk-1a3d09]) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.hiw__cta:where([data-rk-1a3d09]) { text-align: center; margin-top: 3rem; }
.hiw__cta:where([data-rk-1a3d09]) a:where([data-rk-1a3d09]) {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0 1.5rem;
  border-radius: var(--r-md, 12px);
  background: var(--c-primary-light, #d4a017);
  color: var(--c-on-primary, #0a0a0a);
  font-weight: 650;
  text-decoration: none;
  transition: background 0.2s ease;
}
.hiw__cta:where([data-rk-1a3d09]) a:hover:where([data-rk-1a3d09]) { background: var(--c-accent, #fef3c7); color: var(--c-on-primary, #0a0a0a); }
.hiw__cta:where([data-rk-1a3d09]) a:focus-visible:where([data-rk-1a3d09]) { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
@container (max-width: 820px) {
  .hiw__steps:where([data-rk-1a3d09]) { grid-template-columns: 1fr; gap: 1.75rem; }
  .hiw__steps:where([data-rk-1a3d09])::before { top: 24px; bottom: 24px; left: 24px; right: auto; width: 1px; height: auto; background: linear-gradient(180deg, color-mix(in srgb, var(--c-primary-light, #d4a017) 60%, transparent), rgb(var(--ink-rgb, 255 255 255) / 0.1)); }
  .hiw__step:where([data-rk-1a3d09]) { flex-direction: row; align-items: flex-start; text-align: left; gap: 1.25rem; }
  .hiw__marker:where([data-rk-1a3d09]) { flex-shrink: 0; margin-bottom: 0; }
  .hiw--dots:where([data-rk-1a3d09]) .hiw__marker:where([data-rk-1a3d09]) { margin: 16px 16px 0; }
}
@media (prefers-reduced-motion: reduce) { .hiw__step:where([data-rk-1a3d09]) { animation: none; } }