Forms
One .joy-form-group per field, with a .joy-label and a .joy-input inside.
Basic field
<div class="joy-form-group">
<label class="joy-label">Email</label>
<input type="email" class="joy-input" placeholder="you@email.com">
</div>With an error
.joy-form-error is hidden by default — add .is-visible via JS when validation fails:
Password is too short.
<div class="joy-form-group">
<label class="joy-label">Password</label>
<input type="password" class="joy-input" value="123">
<span class="joy-form-error is-visible">Password is too short.</span>
</div>Checkbox and link row
<div class="joy-form-row">
<label class="joy-checkbox-label"><input type="checkbox"> Remember me</label>
<a href="#" class="joy-form-link">Forgot password?</a>
</div>Form banner
<div class="joy-form-banner">Check your inbox to confirm your account.</div>
Form footer
<p class="joy-form-footer">No account yet? <a href="#" class="joy-form-link">Sign up</a></p>
Multi-step form
Each step is a .joy-form-step — only the one with .is-active is visible. Switching steps is handled by hand in JS (toggling the class), paired with .joy-step-dots to show progress:
<div class="joy-step-dots">
<span class="joy-step-dot is-active"></span>
<span class="joy-step-dot"></span>
<span class="joy-step-dot"></span>
</div>
<div class="joy-form-step is-active">
<div class="joy-form-group">
<label class="joy-label">Name</label>
<input type="text" class="joy-input" placeholder="Your name">
</div>
</div>function goToStep(index) {
document.querySelectorAll('.joy-form-step').forEach((el, i) => el.classList.toggle('is-active', i === index));
document.querySelectorAll('.joy-step-dot').forEach((el, i) => el.classList.toggle('is-active', i === index));
}