Components · Atom
Button
The primary action element in HolidaySeva. Buttons trigger bookings, submit forms, and guide users through every flow — from search to checkout.
<button class="btn btn-primary">Book Now</button>.
You give it a class to choose its colour and style. That's it. Everything else — hover effects, animations,
focus rings — is handled automatically by button.css and button.js.
Step-by-step
Quick Start — 3 steps to your first button
Follow these three steps and you'll have a working button in under 2 minutes, even if you've never done this before.
<head><head> section of your HTML file,
after your main design-system.css link.
</body> (optional but recommended)<button> with two classes.btn as the base class, then add a variant class like .btn-primary.
The label inside the tag is what the user sees. Done.
<!-- Step 1: In <head> -->
<link rel="stylesheet" href="https://holidayseva.com/UI/Atom/button/button.css">
<!-- Step 2: Before </body> -->
<script src="https://holidayseva.com/UI/Atom/button/button.js"></script>
<!-- Step 3: Your button -->
<button class="btn btn-primary">Book Now</button>
<button> HTML element. Never use a <div> or <span>
styled to look like a button — that breaks keyboard navigation and screen readers.
How it's built
Anatomy
Every button is made of up to four parts. You control which parts appear by adding classes or child elements.
<span class="btn-badge"> at the end of the label.button.js adds this animated spinner automatically. You never need to write it yourself.Design tokens
Specifications
These are the exact values used in button.css for the default md size. You should never need to override these — they're already correct.
All variants
Variants
There are 10 variants. Each has a single, specific role. Using the wrong variant in the wrong place will confuse users — so read each "When to use" carefully.
- One
btn-primaryper view - Pair with
btn-outlineorbtn-ghostfor secondary actions - Use
btn-dangeronly for delete/cancel - Use
btn-accentonly for messaging
- Two
btn-primarybuttons next to each other - Use
btn-dangerfor a regular edit action - Mix
btn-accentwithbtn-primaryin the same action row - Use
btn-linkto trigger actions (use it only for navigation)
btn-primary
<button class="btn btn-primary">Book Now</button>
btn-secondary<button class="btn btn-secondary">Explore Stays</button>
btn-outline<button class="btn btn-outline">Save to Wishlist</button>
btn-ghost · btn-ghost-primary<button class="btn btn-ghost">Learn More</button>
<button class="btn btn-ghost-primary">View All Listings</button>
btn-soft<button class="btn btn-soft">Apply Filters</button>
btn-accent<button class="btn btn-accent">Contact Host</button>
btn-danger · btn-danger-outline<button class="btn btn-danger">Cancel Booking</button>
<button class="btn btn-danger-outline">Remove Listing</button>
btn-white<button class="btn btn-white">Explore Destinations</button>
btn-link<button class="btn btn-link">View full cancellation policy</button>
Sizing
Sizes
There are 5 sizes. You don't need a size class for the default — just .btn .btn-primary
gives you the standard 44px button. Only add a size class when you need something bigger or smaller.
60px · Hero CTAs
52px · Section CTAs
44px · Standard
36px · Cards & rows
28px · Tags only
<!-- No size class = default 44px -->
<button class="btn btn-primary">Default</button>
<!-- Bigger -->
<button class="btn btn-primary btn-xl">XL · 60px</button>
<button class="btn btn-primary btn-lg">Large · 52px</button>
<!-- Smaller -->
<button class="btn btn-primary btn-sm">Small · 36px</button>
<button class="btn btn-primary btn-xs">XS · 28px</button>
Add .btn-block to make a button stretch to fill its container's width.
Commonly used in checkout modals, mobile forms, and bottom action bars.
<!-- Just add btn-block to any button -->
<button class="btn btn-primary btn-block">Confirm & Pay</button>
Composition
Icons & Badges
You can add an SVG icon before or after the label, an icon-only button (no text), or a badge showing a count. Icons inherit the button's colour automatically — you never need to set their colour manually.
<!-- Leading icon: place the <svg> BEFORE the label text -->
<button class="btn btn-primary">
<svg class="btn-icon" aria-hidden="true">...</svg>
Search
</button>
<!-- Trailing icon: place the <svg> AFTER the label text -->
<button class="btn btn-outline">
Save to List
<svg class="btn-icon" aria-hidden="true">...</svg>
</button>
<!-- Icon-only: add btn-icon-only + always write aria-label -->
<button class="btn btn-outline btn-icon-only"
aria-label="Share listing">
<svg class="btn-icon" aria-hidden="true">...</svg>
</button>
<!-- Badge: wrap the count in a span.btn-badge at the end -->
<button class="btn btn-secondary">
Cart <span class="btn-badge">3</span>
</button>
aria-label="…"
on the <button> element itself — this is what gets read aloud.
Interactivity
States
A button can be in six states. Hover, focus, and active are fully automatic. You only need to write code for Disabled and Loading.
Add the disabled attribute directly on the <button> tag.
CSS automatically reduces opacity to 38% and changes the cursor to "not-allowed".
The button becomes unclickable — no extra JavaScript needed.
<!-- Just add the "disabled" attribute -->
<button class="btn btn-primary" disabled>Book Now</button>
<!-- For <a> tags (links styled as buttons), do this instead: -->
<a class="btn btn-primary"
aria-disabled="true"
tabindex="-1">Book Now</a>
When a user clicks Book and your page sends a request to a server, show a loading state.
Call ButtonSystem.loading(btn, true) to start it — the JS automatically injects
the spinner, disables the button, and announces "busy" to screen readers.
Call ButtonSystem.loading(btn, false) when the server responds to restore it.
// Manual control (most common approach)
const btn = document.querySelector('#bookBtn');
// When your fetch/API call starts:
ButtonSystem.loading(btn, true);
// When your fetch/API call finishes:
ButtonSystem.loading(btn, false);
// ── OR ── Auto-trigger with just HTML attributes (no JS needed)
<button class="btn btn-primary"
data-loading-on-click
data-loading-text="Booking…">
Book Now
</button>
Use .btn-toggle when a button can be switched between selected and unselected —
like a room-type selector. Add the group inside a .btn-group wrapper.
button.js automatically handles the selected highlight and ARIA state.
<!-- Wrap in btn-group, use btn-toggle on each button -->
<!-- Add is-selected + aria-pressed="true" to the default -->
<div class="btn-group">
<button class="btn btn-toggle is-selected"
aria-pressed="true">Entire Place</button>
<button class="btn btn-toggle"
aria-pressed="false">Private Room</button>
<button class="btn btn-toggle"
aria-pressed="false">Shared Room</button>
</div>
button.js fires a custom event called btn-toggle
with a detail.pressed value of true or false.
You can listen to it exactly like a regular click or change event:
btn.addEventListener('btn-toggle', e => console.log(e.detail.pressed))
Grouping
Button Groups
Two ways to group buttons: spaced (with gap between) or attached (merged into one segmented control with borders touching).
.btn-groupWraps buttons in a flex container with 10px gaps. Use for action rows like Confirm + Save Draft + Cancel.
.btn-group-attachedButtons share borders and form a single segmented control. Use for mutually exclusive filter options like Day / Week / Month.
<!-- Spaced: gap between each button -->
<div class="btn-group">
<button class="btn btn-primary">Confirm</button>
<button class="btn btn-outline">Save Draft</button>
<button class="btn btn-ghost">Cancel</button>
</div>
<!-- Attached: borders merge, looks like one control -->
<div class="btn-group-attached">
<button class="btn btn-outline">Day</button>
<button class="btn btn-outline">Week</button>
<button class="btn btn-outline">Month</button>
</div>
JS Reference
JavaScript API
button.js loads automatically when you include the script tag. It exposes one
public method and one custom event. Everything else runs silently in the background.
aria-busy="true" for screen readers
aria-busy
event.detail.pressed (boolean). Listen with addEventListener('btn-toggle', fn)
Setup
Integration
Where the files live, and what to add to your colours file to keep tokens consistent across all components.
├── header.php
├── left_sidebar.php
├── right_sidebar.php
├── drawer_sidebar.php
├── footer.php
├── design-system.css
└── Atom/
└── button/
├── button.php ← this page
├── button.css ← all styles
└── button.js ← loading/toggle/ripple
These variables are used by button.css but aren't yet in your central colors.css.
Add them once — they'll be picked up by buttons everywhere automatically.
/* Primary hover & pressed states */
--color-primary-hover: #E8314F;
--color-primary-pressed: #CC2B47;
/* Ghost background on hover & press */
--color-ghost-hover-bg: rgba(34, 34, 34, 0.06);
--color-ghost-pressed-bg: rgba(34, 34, 34, 0.10);
/* Danger button interaction states */
--color-danger-hover: #A82C10;
--color-danger-pressed: #8E240D;
/* Disabled state surfaces */
--color-surface-disabled: #F5F5F5;
--color-text-disabled: #B0B0B0;
--color-border-disabled: #E0E0E0;
A11y
Accessibility
The button system is built to WCAG 2.1 AA. The table below shows what's handled automatically and what you are responsible for. Green = automatic. Red = your job.
| Requirement | How it's handled | Your job |
|---|---|---|
| Touch target size | ✅ Auto — default md is 44px, the Apple minimum | Don't override height below 44px on mobile |
| Keyboard focus ring | ✅ Auto — 3px ring appears on keyboard navigation only | Never write outline: none without adding your own ring |
| Colour contrast | ✅ Auto — all variants pass 4.5:1 minimum ratio | If you override colours, re-test contrast at webaim.org |
| Disabled state | ✅ Auto — opacity and cursor handled by CSS | ⚑ You — add native disabled attribute to the element |
| Loading state | ✅ Auto — button.js sets aria-busy="true" |
Use ButtonSystem.loading() — don't manually add a spinner |
| Toggle state | ✅ Auto — button.js keeps aria-pressed in sync |
⚑ You — set the correct initial aria-pressed value in markup |
| Icon-only buttons | ❌ Not automatic | ⚑ You — always add aria-label="…" on the button element |
<button> element.
If you use a <div> or <span>, keyboard users can't tab to it,
screen readers won't announce it as a button, and you'd need to manually write many lines of ARIA
and event-handler code to replicate what the browser gives you for free.