@onxvc/stride
v0.0.1
Published
Zero-config multi-step form library for vanilla JavaScript. Built for no-code tools like Webstudio and Webflow.
Downloads
264
Maintainers
Readme
@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.
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>):
<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:
<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:
<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":
<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:
<!-- 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:
/* 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:
<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="[email protected]">
<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:
[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:
/* More specific selector to override Webstudio styles */
form[data-stride="multistep"] [data-stride="step"].is-active {
display: block;
}Testing Your Styles
- Always test in Webstudio's preview mode
- Check mobile views separately (styles may differ)
- 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:
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:
<!-- 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:
console.log('Stride controller:', window.stride?.instances);
const forms = document.querySelectorAll('[data-stride="multistep"]');
console.log('Forms found:', forms.length);Check active step:
const form = document.querySelector('[data-stride="multistep"]');
const activeStep = form.querySelector('[data-stride="step"].is-active');
console.log('Active step:', activeStep);Monitor form events:
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:
<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--invalidclass - The first invalid field is focused
stride:invalidevent 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:
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:
<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()MutationObserverElement.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-hiddenfor hidden steps - ✅ Auto-focus for keyboard navigation
- ✅ Form validation feedback
- ✅ Keyboard support (Enter/Cmd+Enter)
Styling Guide
Basic Styling
/* 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
/* 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
/* 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:
- Fork the repo
- Create a feature branch
- Add tests for new features
- Ensure tests pass:
bun test - Create a changeset:
bun run changeset - Submit a pull request
Support
License
MIT © 2025
