npm package: @onxvc/stride
Description: Zero-config multi-step form library for vanilla JavaScript. Built for no-code tools like Webstudio and Webflow.
Author: onxvc
Latest version: 0.0.1

# @onxvc/stride

A zero-config, attribute-based multi-step form library for **Webstudio**. Built to seamlessly integrate with Webstudio's visual builder and client-side navigation.

[![npm version](https://img.shields.io/npm/v/@onxvc/stride)](https://www.npmjs.com/package/@onxvc/stride)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Zero Configuration** - Add script tag, use data attributes. Works immediately in Webstudio.
- **Native HTML Validation** - Leverages built-in constraint validation API
- **Keyboard Navigation** - Press Enter to advance, Cmd+Enter to submit
- **Auto-focus** - Automatically focuses the first input in each step
- **Progress Indicators** - Built-in progress dots with custom text bindings
- **Accessibility** - Full ARIA support (aria-hidden, proper focus management)
- **Custom Events** - Extensible event system for advanced integrations
- **Multiple Forms** - Support for multiple independent forms on same page
- **Client-side Nav** - Works seamlessly with Webstudio's client-side navigation
- **Lightweight** - 4.06 KB brotli bundle, zero dependencies

## Installation for Webstudio

Add this script tag in Webstudio's **Custom Code** section (typically in `<head>`):

```html
<script async type="module" src="https://cdn.jsdelivr.net/npm/@onxvc/stride"></script>
```

**That's it!** Stride auto-initializes and detects all forms on the page. No configuration needed.

## Getting Started in Webstudio

### Step 1: Create Your Form Structure

Add a **Form** element in Webstudio with the `data-stride="multistep"` attribute:

```html
<form data-stride="multistep" data-stride-enter="true" data-stride-scroll-top="true">
  <!-- Steps go here -->
</form>
```

**Pro Tip**: Use Webstudio's **Set Attribute** panel to add these attributes.

### Step 2: Add Step Containers

Create **Box** elements for each step with `data-stride="step"` attribute:

```html
<div data-stride="step">
  <h2>Step 1</h2>
  <p>Enter your information</p>
  <input type="text" name="name" required>
  <button type="button" data-stride="next">Next</button>
</div>
```

For info-only steps (no validation), add `data-stride-card="true"`:

```html
<div data-stride="step" data-stride-card="true">
  <h2>Welcome!</h2>
  <p>Let's get started</p>
  <button type="button" data-stride="next">Continue</button>
</div>
```

### Step 3: Add Navigation Buttons

Use **Button** elements with proper attributes:

```html
<!-- Back button (hides on first step) -->
<button type="button" data-stride="back">Back</button>

<!-- Next/Continue button (hides on last step) -->
<button type="button" data-stride="next">Next</button>

<!-- Submit button (last step only) -->
<button type="submit" data-stride="submit">Submit</button>
```

**Important**: Place buttons **inside each step** for automatic visibility management.

### Step 4: Add Required CSS

Add this CSS to Webstudio's **Custom CSS** section:

```css
/* Hide all steps by default */
[data-stride="step"] {
  display: none;
}

/* Show only active step */
[data-stride="step"].is-active {
  display: block;
}

/* Hide back button on first step */
.stride--first-step [data-stride="back"] {
  display: none;
}

/* Hide next/submit button on last step */
.stride--last-step [data-stride="next"],
.stride--last-step [data-stride="submit"] {
  display: none;
}
```

That's all the CSS you need! Stride handles button visibility automatically via CSS classes.

### Step 5: Test in Preview

Use Webstudio's preview mode to test your form. Click "Next" to navigate between steps.

## Complete Webstudio Example

Here's a ready-to-use multi-step contact form:

```html
<form 
  data-stride="multistep" 
  data-stride-enter="true" 
  data-stride-scroll-top="true"
  id="contact-form"
>

  <!-- Step 1: Welcome (Card - no validation) -->
  <div data-stride="step" data-stride-card="true">
    <h2>Contact Us</h2>
    <p>We'd love to hear from you. Let's get started.</p>
    <button type="button" data-stride="next">Start</button>
  </div>

  <!-- Step 2: Contact Info -->
  <div data-stride="step">
    <label for="name">Full Name</label>
    <input type="text" id="name" name="name" required placeholder="John Doe">

    <label for="email">Email</label>
    <input type="email" id="email" name="email" required placeholder="john@example.com">

    <div style="display: flex; gap: 12px;">
      <button type="button" data-stride="back">Back</button>
      <button type="button" data-stride="next">Continue</button>
    </div>
  </div>

  <!-- Step 3: Message -->
  <div data-stride="step">
    <label for="message">Your Message</label>
    <textarea id="message" name="message" required rows="4" placeholder="How can we help?"></textarea>

    <div style="display: flex; gap: 12px;">
      <button type="button" data-stride="back">Back</button>
      <button type="submit" data-stride="submit">Send Message</button>
    </div>
  </div>
</form>

<script async type="module" src="https://cdn.jsdelivr.net/npm/@onxvc/stride"></script>

<script type="module">
  // Optional: Listen to form completion
  document.getElementById('contact-form').addEventListener('stride:complete', (e) => {
    const data = Object.fromEntries(e.detail.data);
    console.log('Form submitted:', data);
    // Send to your server/API here
  });
</script>

<style>
  /* Critical CSS for Webstudio */
  [data-stride="step"] {
    display: none;
  }

  [data-stride="step"].is-active {
    display: block;
  }

  .stride--first-step [data-stride="back"] {
    display: none;
  }

  .stride--last-step [data-stride="next"],
  .stride--last-step [data-stride="submit"] {
    display: none;
  }

  /* Optional: Smooth fade transition */
  [data-stride="step"].is-active {
    animation: fadeIn 0.3s ease;
  }

  @keyframes fadeIn {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
  }
</style>
```

## Webstudio Styling Tips

### Using Webstudio's CSS Variables

You can use Webstudio's CSS variables for consistent styling:

```css
[data-stride="step"].is-active {
  display: block;
  background: var(--gray-5);
  padding: var(--spacing-medium);
}
```

### Handling CSS Conflicts

If you apply Webstudio styles that set `display` property directly on step elements, they may conflict with Stride's visibility management.

**Solution**: Let Stride manage visibility - don't apply display styles to step containers directly.

If you need to override, use more specific selectors:

```css
/* More specific selector to override Webstudio styles */
form[data-stride="multistep"] [data-stride="step"].is-active {
  display: block;
}
```

### Testing Your Styles

1. Always test in Webstudio's preview mode
2. Check mobile views separately (styles may differ)
3. Use DevTools inspector to see which styles are being applied

## How It Works with Webstudio's Navigation

### Client-Side Navigation

Stride automatically handles Webstudio's client-side navigation:

- ✅ **Auto-detection**: Detects forms when navigating to a new page
- ✅ **Auto-init**: Initializes forms automatically without manual code
- ✅ **Auto-cleanup**: Cleans up form instances when navigating away
- ✅ **Fresh state**: Each visit to a form page starts at step 1

### Important Behavior

- **State resets on navigation**: Form progress is lost when you navigate to another page (by design for security and simplicity)
- **No state persistence**: Progress is not saved between page visits
- **Dynamic steps**: Steps added after page load are not automatically detected

### Workaround: Save Progress Manually

If you need to preserve form progress, use localStorage:

```javascript
const form = document.querySelector('[data-stride="multistep"]');

// Save progress when advancing
form.addEventListener('stride:before-next', (e) => {
  localStorage.setItem('form-progress', e.detail.toIndex);
});

// Restore progress on page load
form.addEventListener('stride:init', () => {
  const saved = localStorage.getItem('form-progress');
  if (saved) {
    // Get instance from window reference
    const controller = window.stride;
    const instance = controller?.instances.get(form);
    instance?.goTo(parseInt(saved, 10));
  }
});
```

## Multiple Forms on Same Page

You can have multiple independent forms on the same page:

```html
<!-- Contact Form -->
<form data-stride="multistep" id="contact-form">
  <div data-stride="step">
    <h2>Contact Us</h2>
    <!-- Form fields -->
  </div>
</form>

<!-- Feedback Form -->
<form data-stride="multistep" id="feedback-form">
  <div data-stride="step">
    <h2>Feedback</h2>
    <!-- Form fields -->
  </div>
</form>
```

Each form maintains its own state and navigates independently.

## Debugging in Webstudio

Open browser DevTools (F12) and check:

**Check if Stride initialized:**

```javascript
console.log('Stride controller:', window.stride?.instances);
const forms = document.querySelectorAll('[data-stride="multistep"]');
console.log('Forms found:', forms.length);
```

**Check active step:**

```javascript
const form = document.querySelector('[data-stride="multistep"]');
const activeStep = form.querySelector('[data-stride="step"].is-active');
console.log('Active step:', activeStep);
```

**Monitor form events:**

```javascript
const form = document.querySelector('[data-stride="multistep"]');

form.addEventListener('stride:init', (e) => {
  console.log('✅ Form initialized');
});

form.addEventListener('stride:invalid', (e) => {
  console.log('❌ Validation failed', e.detail);
});

form.addEventListener('stride:complete', (e) => {
  console.log('✅ Form submitted', e.detail);
});
```

## Configuration

### Form Attributes

| Attribute | Default | Purpose |
|-----------|---------|---------|
| `data-stride="multistep"` | - | **Required** - Marks form as multi-step |
| `data-stride-enter="true"` | `true` | Enable Enter key to advance |
| `data-stride-scroll-top="true"` | `false` | Auto-scroll to top on navigation |
| `data-stride-count-card="true"` | `true` | Include card steps in progress count |

### Step Attributes

| Attribute | Default | Purpose |
|-----------|---------|---------|
| `data-stride="step"` | - | **Required** - Marks step container |
| `data-stride-card="true"` | `false` | Skip validation for this step |
| `data-stride-index="n"` | auto | Explicit step order |

### Button Attributes

| Attribute | Purpose |
|-----------|---------|
| `data-stride="next"` | Advance to next step |
| `data-stride="back"` | Go back to previous step |
| `data-stride="submit"` | Submit the form |

### CSS Classes

Stride automatically applies these classes:

| Class | Applied To | Purpose |
|-------|------------|---------|
| `.stride` | Form root | Identifies Stride form |
| `.stride-step` | Every step | Marks step containers |
| `.is-active` | Current step | Shows visible step |
| `.is-complete` | Previous steps | Marks visited steps |
| `.stride--invalid` | Form | Shows validation errors |
| `.stride--first-step` | First step | For hiding back button |
| `.stride--last-step` | Last step | For hiding next button |
| `.stride--step-{n}` | Form | Current step number (1-indexed) |

## Validation

Stride uses **native HTML validation**. All standard constraints work:

```html
<input type="text" required placeholder="Required field">
<input type="email" required placeholder="Must be valid email">
<input type="number" min="0" max="100" placeholder="0-100">
<input type="text" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" placeholder="Phone">
<textarea required minlength="10" placeholder="At least 10 characters"></textarea>
```

When validation fails:
- The form gets `.stride--invalid` class
- The first invalid field is focused
- `stride:invalid` event fires
- Navigation is blocked until errors are fixed

## Keyboard Shortcuts

When `data-stride-enter="true"` (default):

- **Enter** - Advance to next step
- **Cmd/Ctrl + Enter** - Submit form (on last step)

## Events

Stride dispatches custom events you can listen to:

```javascript
const form = document.querySelector('[data-stride="multistep"]');

// Form initialized
form.addEventListener('stride:init', (e) => {
  console.log('Form ready', e.detail);
});

// Before step navigation
form.addEventListener('stride:before-next', (e) => {
  console.log('Moving to step', e.detail.toIndex);
});

form.addEventListener('stride:before-back', (e) => {
  console.log('Going back to step', e.detail.toIndex);
});

// After navigation
form.addEventListener('stride:after-next', (e) => {
  console.log('Now on step', e.detail.currentIndex);
});

// Validation failed
form.addEventListener('stride:invalid', (e) => {
  console.log('Validation errors:', e.detail.invalidElements);
});

// Form submitted
form.addEventListener('stride:complete', (e) => {
  const data = Object.fromEntries(e.detail.data);
  console.log('Form data:', data);
});
```

## Progress Indicators

Add visual progress indicators to your form:

```html
<form data-stride="multistep">
  <!-- Progress dots -->
  <div data-stride="progress">
    <div data-stride="indicator"></div>
    <div data-stride="indicator"></div>
    <div data-stride="indicator"></div>
  </div>

  <!-- Step counter -->
  <p>Step <span data-stride-text="current-step">1</span> of <span data-stride-text="total-steps">3</span></p>

  <!-- Steps... -->
</form>

<style>
  [data-stride="indicator"] {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #ddd;
    margin: 0 4px;
    transition: background 0.2s;
  }

  [data-stride="indicator"].is-current,
  [data-stride="indicator"].is-complete {
    background: #3b82f6;
  }
</style>
```

## Browser Support

- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+

Uses modern APIs without polyfills:
- `HTMLFormElement.reportValidity()`
- `MutationObserver`
- `Element.matches()`
- ES2020 syntax

## Performance

- **Bundle size**: 4.06 KB (brotli compressed)
- **Zero dependencies** - Uses vanilla JavaScript
- **Zero runtime overhead** - Event delegation only
- **No polyfills** - Uses native browser APIs

## Limitations

- **Form state**: Not persisted between page navigations in Webstudio
- **Dynamic steps**: Steps added after page load won't auto-initialize
- **No localStorage**: Progress is not automatically saved

These are intentional design decisions for security and simplicity. See workarounds above if needed.

## Accessibility

Stride is built with accessibility in mind:

- ✅ Semantic HTML forms
- ✅ `aria-hidden` for hidden steps
- ✅ Auto-focus for keyboard navigation
- ✅ Form validation feedback
- ✅ Keyboard support (Enter/Cmd+Enter)

## Styling Guide

### Basic Styling

```css
/* Hide steps */
[data-stride="step"] {
  display: none;
}

/* Show active step */
[data-stride="step"].is-active {
  display: block;
}

/* Add transition */
[data-stride="step"].is-active {
  animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
```

### Button Visibility

```css
/* Hide back on first step (Stride applies .stride--first-step) */
.stride--first-step [data-stride="back"] {
  display: none;
}

/* Hide next on last step (Stride applies .stride--last-step) */
.stride--last-step [data-stride="next"],
.stride--last-step [data-stride="submit"] {
  display: none;
}
```

### Validation Styling

```css
/* Show validation errors */
.stride--invalid input:invalid {
  border-color: #ef4444;
  background-color: #fef2f2;
}

.stride--invalid input:invalid::placeholder {
  color: #fca5a5;
}
```

## Roadmap

### Future Features

- Clickable progress indicators (jump to step)
- Step completion callbacks
- Conditional step visibility
- Step transitions/animations
- Custom validators
- Session persistence (localStorage)

## Contributing

Contributions welcome! Please:

1. Fork the repo
2. Create a feature branch
3. Add tests for new features
4. Ensure tests pass: `bun test`
5. Create a changeset: `bun run changeset`
6. Submit a pull request

## Support

- 📖 [Full Documentation](https://github.com/onxvc/stride)
- 🐛 [Report Issues](https://github.com/onxvc/stride/issues)
- 💬 [Discussions](https://github.com/onxvc/stride/discussions)

## License

MIT © 2025

---

## Repository

- **GitHub**: [https://github.com/onxvc/stride](https://github.com/onxvc/stride)
- **npm**: [https://www.npmjs.com/package/@onxvc/stride](https://www.npmjs.com/package/@onxvc/stride)
- **CDN**: [https://cdn.jsdelivr.net/npm/@onxvc/stride](https://cdn.jsdelivr.net/npm/@onxvc/stride)