Zero-config multi-step form library for vanilla JavaScript. Built for no-code tools like Webstudio and Webflow.
npm install @onxvc/strideA zero-config, attribute-based multi-step form library for Webstudio. Built to seamlessly integrate with Webstudio's visual builder and client-side navigation.


- 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
Add this script tag in Webstudio's Custom Code section (typically in ):
``html`
That's it! Stride auto-initializes and detects all forms on the page. No configuration needed.
Add a Form element in Webstudio with the data-stride="multistep" attribute:
`html`
Pro Tip: Use Webstudio's Set Attribute panel to add these attributes.
Create Box elements for each step with data-stride="step" attribute:
` Enter your informationhtml`
Step 1
For info-only steps (no validation), add data-stride-card="true":
` Let's get startedhtml`
Welcome!
Use Button elements with proper attributes:
`html
`
Important: Place buttons inside each step for automatic visibility management.
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.
Use Webstudio's preview mode to test your form. Click "Next" to navigate between steps.
Here's a ready-to-use multi-step contact form:
`html
data-stride="multistep"
data-stride-enter="true"
data-stride-scroll-top="true"
id="contact-form"
>
We'd love to hear from you. Let's get started.
`
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);
}
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;
}
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
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
- 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
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));
}
});
`
You can have multiple independent forms on the same page:
`html
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
$3
| 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 |$3
| 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 |$3
| Attribute | Purpose |
|-----------|---------|
|
data-stride="next" | Advance to next step |
| data-stride="back" | Go back to previous step |
| data-stride="submit" | Submit the form |$3
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
`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 fixedKeyboard 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
`Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Uses modern APIs without polyfills:
-
HTMLFormElement.reportValidity()
- MutationObserver
- Element.matches()
- ES2020 syntaxPerformance
- 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
$3
`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; }
}
`$3
`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;
}
`$3
`css
/ Show validation errors /
.stride--invalid input:invalid {
border-color: #ef4444;
background-color: #fef2f2;
}.stride--invalid input:invalid::placeholder {
color: #fca5a5;
}
`Roadmap
$3
- 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`- 📖 Full Documentation
- 🐛 Report Issues
- 💬 Discussions
MIT © 2025
---
- GitHub: https://github.com/onxvc/stride
- npm: https://www.npmjs.com/package/@onxvc/stride
- CDN: https://cdn.jsdelivr.net/npm/@onxvc/stride