Grid System
12-column responsive grid. .container — full width with wide horizontal inset (--sf-container-padding: 1rem → 3rem → 7rem). .container-fluid — tighter inset (--sf-gutter: 1rem → 1.5rem). Put .container inside full-bleed parents for background + inset content. .row has no default margin/padding; .col-* uses 0.5rem padding. .gap-* / .g-* on .row add gutters between columns.
<!-- Full-bleed section + wide content inset (.container) -->
<section class="bg-gradient-primary">
<div class="container">
<div class="row">
<div class="col-12 col-md-6">Col A</div>
<div class="col-12 col-md-6">Col B</div>
</div>
</div>
</section>
<!-- Tighter edge padding (.container-fluid) — e.g. navbar -->
<header class="navbar">
<div class="container-fluid">…</div>
</header>
<div class="container">
<div class="row gap-2">
<div class="col-6">col-6</div>
<div class="col-6">col-6</div>
</div>
</div>
Typography
Headings, display sizes, gradient text, and text utilities powered by Inter.
H1 — The quick brown fox
H2 — The quick brown fox
H3 — The quick brown fox
H4 — The quick brown fox
H5 — The quick brown fox
Lead paragraph — larger text for introductions and hero subtitles.
Regular body text. Use heading classes for hierarchy, utility classes for emphasis.
Muted text using .text-muted
<!-- Heading & display (matches preview) -->
<div class="display-1">Display <span class="text-gradient">Gradient</span></div>
<p class="h1">H1 — The quick brown fox</p>
<p class="h2">H2 — The quick brown fox</p>
<p class="h3">H3 — The quick brown fox</p>
<p class="lead">Lead paragraph — larger text for introductions.</p>
<p class="text-muted">Regular body text.</p>
<!-- Additional text utilities -->
<span class="text-gradient">Blue → Purple → Cyan</span>
<p class="fw-semibold">Semibold 600</p>
<span class="label-highlight">✨ New Feature</span>
Masthead
Page header banner with .bg-gradient-primary background, white title/lead text, and optional actions. After a fixed navbar, add .navbar-spacer then .masthead — vertical padding comes from framework tokens (--sf-masthead-padding-top/bottom), not inline styles. Use .masthead-compact for a shorter variant; .masthead-offset-nav only if you skip .navbar-spacer.
<!-- Fixed navbar + spacer, then masthead (recommended) -->
<nav class="navbar">…</nav>
<div class="navbar-spacer" aria-hidden="true"></div>
<header class="masthead bg-gradient-primary">
<div class="masthead-content container">
<h1 class="masthead-title">Page title</h1>
<p class="masthead-lead">Short introduction text.</p>
<div class="masthead-actions">
<a href="#" class="btn btn-primary">Action</a>
</div>
</div>
</header>Jumbrotton
Hero-style content block with .bg-gradient-primary (same as Masthead). Use .jumbrotton for a single centered column, or .jumbrotton.jumbrotton-2col for a two-column layout with media.
One column
Centered headline, lead copy, and call-to-action buttons in a single column.
Two columns
Text and actions on the left; image or illustration on the right (stacks on mobile).
<!-- 1 column -->
<div class="jumbrotton bg-gradient-primary rounded-3">
<h2 class="jumbrotton-title">Title</h2>
<p class="jumbrotton-lead">Lead text.</p>
<div class="jumbrotton-actions">
<button class="btn btn-primary">Action</button>
</div>
</div>
<!-- 2 columns -->
<div class="jumbrotton jumbrotton-2col bg-gradient-primary rounded-3">
<div>
<h2 class="jumbrotton-title">Title</h2>
<p class="jumbrotton-lead">Lead text.</p>
<div class="jumbrotton-actions">…</div>
</div>
<div class="jumbrotton-media">
<img src="…" alt="">
</div>
</div>Buttons
Use .btn with color, outline, gradient, size (.btn-sm, .btn-md default, .btn-lg, .btn-xl), and icon variants. All buttons include a ripple effect on click.
<!-- Solid buttons: .btn + .btn-{color} -->
<button type="button" class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-success">Success</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-warning">Warning</button>
<button class="btn btn-info">Info</button>
<button class="btn btn-light">Light</button>
<button class="btn btn-dark">Dark</button><!-- Outline buttons: .btn-outline-{color} -->
<button type="button" class="btn btn-outline-primary">Outline Primary</button>
<button class="btn btn-outline-secondary">Outline Secondary</button>
<button class="btn btn-outline-success">Outline Success</button>
<button class="btn btn-outline-danger">Outline Danger</button>
<button class="btn btn-outline-warning">Outline Warning</button>
<button class="btn btn-outline-info">Outline Info</button>Button sizes — .btn-sm / .btn-md (default) / .btn-lg / .btn-xl
.btn-sm
.btn-md
.btn
.btn-lg
.btn-xl
<!-- Button sizes: .btn-sm / .btn-md / .btn (default) / .btn-lg / .btn-xl -->
<button type="button" class="btn btn-primary btn-sm">Primary</button>
<button type="button" class="btn btn-outline-primary btn-sm">Outline</button>
<button type="button" class="btn btn-primary btn-md">Primary</button>
<button type="button" class="btn btn-outline-primary btn-md">Outline</button>
<!-- Default medium — omit size class or use .btn-md -->
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-primary btn-lg">Primary</button>
<button type="button" class="btn btn-outline-primary btn-lg">Outline</button>
<button type="button" class="btn btn-primary btn-xl">Primary</button>
<button type="button" class="btn btn-outline-primary btn-xl">Outline</button><!-- Gradient, disabled & icon buttons (combine with size classes above) -->
<button type="button" class="btn btn-gradient">Gradient</button>
<button class="btn btn-gradient-purple">Purple</button>
<button class="btn btn-primary" disabled>Disabled</button>
<button class="btn btn-primary btn-icon" title="Icon button" data-tooltip="Icon only">★</button>Offcanvas
Slide-in panel for mobile/tablet navigation (and any side drawer). Trigger with data-bs-toggle="offcanvas" and data-bs-target="#panelId". Backdrop and ESC close are automatic. Submenus: data-toggle on .offcanvas-nav-link / .offcanvas-submenu-link. JS: SuhuFramework.openOffcanvas('id') / closeOffcanvas('id').
<!-- Trigger -->
<button type="button" class="btn btn-primary"
data-bs-toggle="offcanvas"
data-bs-target="#demoOffcanvasPanel">
Open menu
</button>
<!-- Panel: .offcanvas-start | -end | -top | -bottom -->
<div id="demoOffcanvasPanel" class="offcanvas offcanvas-start"
role="dialog" aria-modal="true" aria-label="Menu" aria-hidden="true">
<div class="offcanvas-header">
<span class="navbar-brand">Title</span>
<button type="button" class="offcanvas-close" aria-label="Close">✕</button>
</div>
<div class="offcanvas-body">
<a href="#" class="offcanvas-nav-link">
<span class="link-icon">🏠</span>
<span class="link-text">Home</span>
</a>
<div class="offcanvas-nav-item">
<button type="button" class="offcanvas-nav-link" data-toggle>
<span class="link-text">More</span>
<svg class="oc-arrow" …></svg>
</button>
<div class="offcanvas-submenu">
<a href="#" class="offcanvas-submenu-link">Sub link</a>
</div>
</div>
</div>
<div class="offcanvas-footer">
<a href="#" class="btn btn-primary">Action</a>
</div>
</div>
<!-- With navbar: see Navbar section View code (full copy-paste) -->Carousel
Markup: .carousel → .carousel-wrapper → .carousel-slide. navigation & pagination are on by default (injected if omitted). thumbnail: true builds thumbs from slide images (max visible: 3 mobile / 5 tablet / 7 desktop; swipe when there are more).
<div class="carousel myCarousel">
<div class="carousel-wrapper">
<div class="carousel-slide">
<img src="..." alt="" />
</div>
<div class="carousel-slide">
<img src="..." alt="" />
</div>
<div class="carousel-slide">
<img src="..." alt="" />
</div>
</div>
</div>
<script>
const sfcarousel = new SFCarousel('.myCarousel', {
spaceBetween: 10,
slidesPerView: 1,
gridRows: 1,
loop: true,
autoplay: {
delay: 2500,
disableOnInteraction: false,
},
zoom: 'singleClick',
thumbnail: false,
navigation: true,
pagination: true,
});
</script>Grid: gridRows: 2 + slidesPerView: 2
<div class="carousel myCarouselGrid">
<div class="carousel-wrapper">
<div class="carousel-slide"><img src="..." alt="" /></div>
<div class="carousel-slide"><img src="..." alt="" /></div>
<div class="carousel-slide"><img src="..." alt="" /></div>
<div class="carousel-slide"><img src="..." alt="" /></div>
<div class="carousel-slide"><img src="..." alt="" /></div>
<div class="carousel-slide"><img src="..." alt="" /></div>
</div>
</div>
<script>
new SFCarousel('.myCarouselGrid', {
spaceBetween: 5,
slidesPerView: 2,
gridRows: 2,
loop: true,
zoom: 'singleClick',
thumbnail: false,
navigation: true,
pagination: true,
});
</script>Cards
Light, dark, gradient-border, and feature card variants.
Card with header and footer.
Gradient Border
Hover to see the gradient border intensify.
<!-- Cards: .card-group with multiple .card -->
<div class="card-group">
<div class="card">
<div class="card-body rounded-3">
<h3 class="card-title">Basic Card</h3>
<p class="card-text">Hover to see the lift shadow effect.</p>
<a href="#" class="btn btn-primary mt-3">Learn more</a>
</div>
</div>
<div class="card rounded-3">
<div class="card-header">Header</div>
<div class="card-body rounded-3"><p class="card-text">Card with header and footer.</p></div>
<div class="card-footer d-flex justify-content-end gap-2">
<button type="button" class="btn btn-ghost">Cancel</button>
<button type="button" class="btn btn-primary">Save</button>
</div>
</div>
<div class="card card-gradient-border">
<div class="card-body rounded-3">
<h3 class="card-title">Gradient Border</h3>
<p class="card-text">Hover to see the gradient border intensify.</p>
</div>
</div>
</div>Feature Card
Theme-aware card with gradient icon. Hover for glow border effect.
Custom Icon Color
Change the icon gradient with an inline style override.
<!-- Feature card: .feature-card + .feature-icon (emoji or icon) -->
<div class="card-group">
<div class="feature-card rounded-3">
<div class="feature-icon">⚡</div>
<h3 class="card-title">Feature Card</h3>
<p class="card-text">Theme-aware card with gradient icon.</p>
</div>
<div class="feature-card rounded-3">
<div class="feature-icon" style="background:linear-gradient(135deg,#a855f7,#ec4899);">🎨</div>
<h3 class="card-title">Custom Icon Color</h3>
<p class="card-text">Change the icon gradient with an inline style override.</p>
</div>
</div>Alerts
Contextual feedback messages. Add .alert-dismissible and .alert-dismiss button for a closable alert.
<!-- Alert: .alert-{color}; dismiss with .alert-dismissible + .alert-dismiss -->
<div class="alert alert-primary">
<span class="alert-icon">ℹ️</span>
<div class="alert-content">
<div class="alert-title">Information</div>
A primary alert — for informational messages and tips.
</div>
</div>
<div class="alert alert-success">
<span class="alert-icon">✅</span>
<div class="alert-content">
<div class="alert-title">Success</div>
Your changes have been saved successfully.
</div>
</div>
<div class="alert alert-danger alert-dismissible">
<span class="alert-icon">🚨</span>
<div class="alert-content">
<div class="alert-title">Error</div>
Something went wrong. Click ✕ to dismiss this alert.
</div>
<button class="alert-dismiss" aria-label="Dismiss">✕</button>
</div>
<div class="alert alert-warning">
<span class="alert-icon">⚠️</span>
<div class="alert-content">
<div class="alert-title">Warning</div>
Please review your settings before proceeding.
</div>
</div>Badges & Chips
Small status indicators and interactive chip tags.
<!-- Badge: .badge + .bg-{color} or .badge-{color} -->
<span class="badge rounded-pill bg-primary">Primary</span>
<span class="badge rounded-pill bg-success">Success</span>
<span class="badge rounded-pill bg-danger">Danger</span>
<span class="badge rounded-pill bg-warning">Warning</span>
<span class="badge rounded-pill bg-info">Info</span>
<span class="badge rounded-pill bg-dark">Dark</span>
<span class="badge rounded-pill bg-blue-subtle">Blue subtle</span>
<span class="badge rounded-pill bg-green-subtle">Green subtle</span>
<span class="badge rounded-pill bg-red-subtle">Red subtle</span>
<span class="badge rounded-pill bg-purple-subtle">Purple subtle</span>
<span class="badge rounded-pill bg-amber-subtle">Amber subtle</span><!-- Chip: small tag; .chip-primary / .chip-success for color -->
<span class="chip rounded-pill">Default</span>
<span class="chip rounded-pill chip-primary">Primary</span>
<span class="chip rounded-pill chip-success">✓ Active</span>
<span class="chip rounded-pill chip-danger">✗ Error</span>
<span class="chip rounded-pill">React</span>
<span class="chip rounded-pill">TypeScript</span>
<span class="chip rounded-pill">WordPress</span>
<span class="chip rounded-pill chip-primary">PHP</span>Forms
SuhuFramework form styling (ring-inset, rounded-md, shadow-sm, focus ring) — included in suhu-framework.min.css. Control sizes: .form-control-sm / default / .form-control-lg and .form-select-sm / default / .form-select-lg. Date field uses data-sf-date-picker (initialized by SuhuFramework.init()).
<!-- Form: .form-label + .form-control / .form-select; feedback below field -->
<div class="sf-form-field">
<label class="form-label" for="demo-email">Email address <span class="required">*</span></label>
<input type="email" class="form-control rounded" id="demo-email" placeholder="you@example.com">
</div>
<div class="sf-form-field">
<label class="form-label" for="demo-pass">Password</label>
<input type="password" class="form-control rounded is-valid" id="demo-pass" value="securepwd123" autocomplete="off">
<div class="valid-feedback">✓ Strong password</div>
</div>
<div class="sf-form-field">
<label class="form-label" for="demo-user">Username</label>
<input type="text" class="form-control rounded is-invalid" id="demo-user" value="taken_user">
<div class="invalid-feedback">This username is already taken.</div>
</div>
<div class="sf-form-field">
<label class="form-label" for="demo-fw">Framework</label>
<select class="form-select rounded" id="demo-fw">
<option>SuhuFramework</option>
<option>Starter kit</option>
<option>Pro bundle</option>
</select>
</div>
<!-- Date picker: click month/year to pick (SuhuFramework.init / SFDatePicker) -->
<div class="sf-form-field">
<label class="form-label" for="demo-date">Date</label>
<input type="text"
class="form-control rounded"
id="demo-date"
data-sf-date-picker
data-sf-date-locale="id"
data-sf-date-format="DD/MM/YYYY"
placeholder="DD/MM/YYYY"
readonly>
</div>
<!-- ISO value stored in data-sf-date-value; SFDatePicker.init() after dynamic DOM -->
<div class="sf-form-field">
<label class="form-label" for="demo-msg">Message</label>
<textarea class="form-control rounded" id="demo-msg" rows="3" placeholder="Write your message..."></textarea>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="ch1" checked>
<label class="form-check-label" for="ch1">Accept terms & conditions</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="plan" id="r1" checked>
<label class="form-check-label" for="r1">Monthly plan</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="plan" id="r2">
<label class="form-check-label" for="r2">Annual plan (save 20%)</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="sw1" checked>
<label class="form-check-label" for="sw1">Enable notifications</label>
</div>
<label class="form-label">Volume: <span id="rangeVal">50</span>%</label>
<input type="range" class="form-range w-100" min="0" max="100" value="50"
oninput="document.getElementById('rangeVal').textContent=this.value">Form control sizes — .form-control-sm / default / .form-control-lg
<!-- .form-control sizes -->
<input type="text" class="form-control form-control-sm rounded" placeholder="Small input">
<input type="text" class="form-control rounded" placeholder="Default input">
<input type="text" class="form-control form-control-lg rounded" placeholder="Large input">
<!-- Textarea also accepts size classes -->
<textarea class="form-control form-control-sm rounded" rows="2"></textarea>
<textarea class="form-control form-control-lg rounded" rows="3"></textarea>Form select sizes — .form-select-sm / default / .form-select-lg
<!-- .form-select sizes -->
<select class="form-select form-select-sm rounded">
<option>Small select</option>
</select>
<select class="form-select rounded">
<option>Default select</option>
</select>
<select class="form-select form-select-lg rounded">
<option>Large select</option>
</select>Modals
Accessible dialogs with backdrop blur and spring animation. By default, click outside the dialog or press ESC to close. Use data-modal-backdrop="static" (or class .modal-static-backdrop) to ignore outside clicks.
<!-- Triggers (match preview) -->
<button class="btn btn-primary" data-modal-target="#modal-basic">Open Modal</button>
<button class="btn btn-outline-primary" data-modal-target="#modal-sm">Small</button>
<button class="btn btn-outline-primary" data-modal-target="#modal-lg">Large</button>
<button class="btn btn-gradient-purple" data-modal-target="#modal-form">Form Modal</button>
<button class="btn btn-outline-secondary" data-modal-target="#modal-static">Static backdrop</button>
<!-- Modal -->
<div class="modal-overlay" id="modal-basic">
<div class="modal">
<div class="modal-header">
<h5 class="modal-title">Title</h5>
<button class="modal-close rounded" data-modal-close>✕</button>
</div>
<div class="modal-body">Content here.</div>
<div class="modal-footer">
<button class="btn btn-ghost" data-modal-close>Cancel</button>
<button class="btn btn-primary">Confirm</button>
</div>
</div>
</div>
<!-- Size variants -->
<div class="modal modal-sm">...</div>
<div class="modal modal-lg">...</div>
<div class="modal modal-xl">...</div>
<!-- Static backdrop: outside click does not close -->
<div class="modal-overlay" id="myStaticModal" data-modal-backdrop="static">
<div class="modal">...</div>
</div>
<!-- Or: class="modal-overlay modal-static-backdrop" -->
<!-- Optional: disable ESC (data-modal-keyboard="false") -->Accordion
Collapsible content panels with smooth max-height animation.
<!-- Accordion: click .accordion-button; one open unless data-multi-open -->
<div class="accordion">
<div class="accordion-item">
<div class="accordion-header">
<button class="accordion-button active" aria-expanded="true">
Title
<svg class="accordion-icon">...chevron svg...</svg>
</button>
</div>
<div class="accordion-collapse show">
<div class="accordion-body">Content</div>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header">
<button class="accordion-button" aria-expanded="false">
Another item
<svg class="accordion-icon">...</svg>
</button>
</div>
<div class="accordion-collapse">
<div class="accordion-body">Hidden content</div>
</div>
</div>
</div>
<!-- Allow multiple panels open -->
<div class="accordion" data-multi-open>...</div>Tabs
Underline (.nav-tabs) and pill (.nav-pills) variants. Link tabs to panes with data-target="#id". Align tab lists with .justify-content-* or stretch with .nav-fill / .nav-justified.
Tab alignment
.justify-content-start — Start (default)
.justify-content-center — Center
.justify-content-end — End
.nav-fill — Justify (equal width)
Pills + .justify-content-center
Pills + .nav-fill
<!-- Tab alignment: .justify-content-* / .nav-fill on .nav-tabs or .nav-pills -->
<ul class="nav-tabs justify-content-start">
<li><a class="nav-link active" href="#">Tab 1</a></li>
<li><a class="nav-link" href="#">Tab 2</a></li>
</ul>
<!-- Center -->
<ul class="nav-tabs justify-content-center">...</ul>
<!-- End -->
<ul class="nav-tabs justify-content-end">...</ul>
<!-- Justify / equal width -->
<ul class="nav-tabs nav-fill">...</ul>
<!-- .nav-justified is an alias of .nav-fill -->
<!-- Same utilities on pill tabs -->
<div class="nav-pills justify-content-center">...</div>
<div class="nav-pills nav-fill">...</div>Interactive examples
Overview
Switch between tabs to see the animated underline and fade transition.
- ✅ Responsive grid system
- ✅ SuhuFramework standard classes
- ✅ Light & dark mode
// global: SuhuFramework (alias: Suhu)
SuhuFramework.init()Showing all 48 items.
Showing 32 active items.
Showing 16 archived items.
<!-- Interactive tabs: data-target must match .tab-pane id -->
<ul class="nav-tabs">
<li><a class="nav-link active" href="#t1" data-target="#t1">Overview</a></li>
<li><a class="nav-link" href="#t2" data-target="#t2">Features</a></li>
<li><a class="nav-link" href="#t3" data-target="#t3">Code</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="t1">…Overview content…</div>
<div class="tab-pane" id="t2">…Features content…</div>
<div class="tab-pane" id="t3">…Code content…</div>
</div>
<!-- Pill tabs -->
<div class="nav-pills">
<a class="nav-link active" href="#p1" data-target="#p1">All</a>
<a class="nav-link" href="#p2" data-target="#p2">Active</a>
<a class="nav-link" href="#p3" data-target="#p3">Archived</a>
</div>
<div class="tab-content">
<div class="tab-pane active" id="p1">Showing all 48 items.</div>
<div class="tab-pane" id="p2">Showing 32 active items.</div>
<div class="tab-pane" id="p3">Showing 16 archived items.</div>
</div>Toast
Lightweight notification toasts. Trigger programmatically with SuhuFramework.showToast() or via data-toast-* attributes. Auto-dismiss with a progress bar.
<!-- Toast from button: data-toast-title, data-toast-message, data-toast-type -->
<button type="button" class="btn btn-primary btn-sm"
data-toast-title="Information"
data-toast-message="Your changes have been saved successfully."
data-toast-type="primary">Show Info</button>
<button class="btn btn-success btn-sm"
data-toast-title="Success!"
data-toast-message="The item was created."
data-toast-type="success">Show Success</button>
<button class="btn btn-danger btn-sm"
data-toast-title="Error"
data-toast-message="Something went wrong. Please try again."
data-toast-type="danger">Show Error</button>
<button class="btn btn-warning btn-sm"
data-toast-title="Warning"
data-toast-message="Your session will expire in 5 minutes."
data-toast-type="warning">Show Warning</button>
<!-- Static toast markup (preview; add position styles in layout if needed) -->
<div class="toast toast-success show" style="position:relative;transform:none;opacity:1;">
<div class="toast-header">
<span class="toast-icon">✓</span>
<span class="toast-title">Saved</span>
<span class="toast-time">just now</span>
<button class="toast-close rounded">✕</button>
</div>
<div class="toast-body">Profile updated successfully.</div>
<div class="toast-progress"></div>
</div>Tooltip
CSS-only tooltips via data-tooltip. Position with data-tooltip-pos="top|bottom|left|right". On native <button>, JS wraps a .sf-tooltip-host so the arrow is not clipped inside the control.
.tooltip
<!-- Tooltip CSS: data-tooltip + data-tooltip-pos (top default) -->
<button type="button" class="btn btn-secondary" data-tooltip="Default — top position">Hover me (top)</button>
<button class="btn btn-secondary" data-tooltip="Slides in from below" data-tooltip-pos="bottom">Bottom</button>
<button class="btn btn-secondary" data-tooltip="Left side tooltip" data-tooltip-pos="left">Left</button>
<button class="btn btn-secondary" data-tooltip="Right side tooltip" data-tooltip-pos="right">Right</button>
<span class="badge rounded-pill badge-primary" data-tooltip="Tooltips work on any element">Badge</span>
<code data-tooltip="Code element tooltip" data-tooltip-pos="bottom">.tooltip</code>Popovers
Click-triggered popovers with title and body. Use data-bs-toggle="popover", data-bs-title, data-bs-content, and data-bs-placement (top, bottom, left, right). Click outside or press Escape to dismiss.
<!-- Popover: click to open; click outside / Escape to close -->
<button type="button" class="btn btn-primary"
data-bs-toggle="popover"
data-bs-title="SuhuFramework"
data-bs-content="Lightweight UI kit — one CSS file, one JS file."
data-bs-placement="top">Top</button>
<button type="button" class="btn btn-secondary"
data-bs-toggle="popover"
data-bs-title="Documentation"
data-bs-content="See guide.md for the full component reference."
data-bs-placement="bottom">Bottom</button>
<button type="button" class="btn btn-success"
data-bs-toggle="popover"
data-bs-title="RTL ready"
data-bs-content="Try the Direction section — LTR / RTL toggles inside .sf-preview only."
data-bs-placement="left">Left</button>
<button type="button" class="btn btn-info"
data-bs-toggle="popover"
data-bs-title="Theme modes"
data-bs-content="Light, Dark, or Auto — matches your system preference."
data-bs-placement="right">Right</button>TabBar
Floating bottom navigation for mobile (<992px). On real pages use position: fixed at the viewport bottom; add .tab-bar-spacer so content is not covered. Preview below uses .tab-bar-frame to simulate a phone screen on any viewport.
<!-- Production: fixed to viewport bottom (mobile < 992px) -->
<nav class="tab-bar" aria-label="Bottom navigation">
<a href="#" class="tab-bar-item active">
<span class="tab-bar-icon">🏠</span>
<span class="tab-bar-label">Home</span>
</a>
<a href="#" class="tab-bar-item">
<span class="tab-bar-icon">🔍</span>
<span class="tab-bar-label">Search</span>
</a>
<button type="button" class="tab-bar-item">
<span class="tab-bar-icon">⚙️</span>
<span class="tab-bar-label">Settings</span>
</button>
</nav>
<div class="tab-bar-spacer" aria-hidden="true"></div>
<!-- Docs preview: phone frame (visible on desktop too) -->
<div class="tab-bar-frame">
<div class="tab-bar-frame__content">…</div>
<nav class="tab-bar" aria-label="Bottom navigation">…</nav>
</div>Mobile Animations
Touch-friendly enter/exit animations for mobile views and page transitions. Apply classes directly or toggle them in JS.
In — enter / show
Out — exit / hide
<!-- In (enter) -->
<div class="mobile-fade-left-in">…</div>
<div class="mobile-fade-right-in">…</div>
<div class="mobile-fade-up-in">…</div>
<div class="mobile-fade-down-in">…</div>
<div class="mobile-scale-in">…</div>
<!-- Out (exit) -->
<div class="mobile-fade-left-out">…</div>
<div class="mobile-fade-right-out">…</div>
<div class="mobile-fade-up-out">…</div>
<div class="mobile-fade-down-out">…</div>
<div class="mobile-scale-out">…</div>Theme
Three color schemes: Light, Dark, and Auto (follows OS prefers-color-scheme). Preference is stored in localStorage under sf-theme-mode. Resolved theme is applied via data-theme on <html>.
<!-- Theme: Light / Dark / Auto (stored in localStorage sf-theme-mode) -->
<div class="sf-theme-picker" role="group" aria-label="Color scheme">
<button type="button" class="sf-theme-picker__btn" data-theme-mode-option="light" title="Light mode">☀ Light</button>
<button type="button" class="sf-theme-picker__btn" data-theme-mode-option="dark" title="Dark mode">☾ Dark</button>
<button type="button" class="sf-theme-picker__btn" data-theme-mode-option="auto" title="System preference">◐ Auto</button>
</div>
<!-- Optional: single button cycles light → dark → auto -->
<button type="button" class="btn btn-outline-secondary btn-sm" data-theme-toggle>
<span data-theme-icon>◐</span>
<span data-theme-label>Auto</span>
</button>
<script>
SuhuFramework.setThemeMode('auto'); // 'light' | 'dark' | 'auto'
</script>Direction (LTR / RTL)
Preview only inside .sf-preview — the rest of this page stays LTR. Set dir on a scoped container with data-sf-dir-scope. Utility classes .ms-*, .me-*, .text-start / .text-end use logical properties and mirror under RTL.
Sample layout (scoped preview)
Text aligned with .text-end
<!-- RTL scoped to this area: data-sf-dir-scope (demo page stays LTR) -->
<div class="sf-preview" data-sf-dir-scope data-sf-dir="ltr">
<div class="sf-dir-picker" role="group" aria-label="Text direction preview">
<button type="button" class="sf-dir-picker__btn" data-dir-option="ltr" title="Left to right">LTR</button>
<button type="button" class="sf-dir-picker__btn" data-dir-option="rtl" title="Right to left">RTL</button>
</div>
<div class="d-flex align-items-center gap-2 flex-wrap">
<span class="badge rounded-pill bg-primary">Start</span>
<span class="ms-auto badge rounded-pill bg-secondary">ms-auto</span>
</div>
<p class="text-end">Text aligned with .text-end</p>
</div>
<script>
SuhuFramework.setDirection('rtl'); // entire document — optional
</script>Progress
Animated gradient progress bars. data-width triggers on scroll into view.
<!-- Progress: fill .progress-bar; optional striped/animated -->
<div class="progress rounded-pill">
<div class="progress-bar" data-width="75%"></div>
</div>
<!-- Color variants -->
<div class="progress-bar progress-bar-success" data-width="90%"></div>
<div class="progress-bar progress-bar-danger" data-width="30%"></div>
<div class="progress-bar progress-bar-warning" data-width="60%"></div>
<div class="progress-bar progress-bar-info" data-width="45%"></div>
<!-- Size variants -->
<div class="progress rounded-pill progress-sm">...</div>
<div class="progress rounded-pill progress-lg">...</div>Spinners
Border and grow spinners in multiple sizes and color variants.
<!-- Spinner: border or grow; color via .spinner-{color} -->
<div class="spinner-border spinner-primary"></div>
<div class="spinner-border spinner-success"></div>
<div class="spinner-border spinner-danger"></div>
<div class="spinner-border spinner-warning"></div>
<div class="spinner-border spinner-sm spinner-primary"></div>
<div class="spinner-border spinner-lg spinner-primary"></div>
<div class="spinner-grow spinner-primary"></div>
<div class="spinner-grow spinner-success"></div>
<button type="button" class="btn btn-primary" disabled>
<span class="spinner-border spinner-sm" role="status" aria-hidden="true"></span>
Loading...
</button>Tables
Mobile-first layout: datatable toolbar stacks search and actions on small screens; table scrolls inside .table-responsive. Enable features via .table-datatable data attributes (SuhuFramework.init()).
Basic table
<!-- Basic table: wrap in .table-responsive for mobile scroll -->
<div class="table-responsive">
<table class="table rounded table-striped table-hover">
<thead>
<tr><th scope="col">#</th><th scope="col">Name</th><th scope="col">Role</th><th scope="col">Status</th><th scope="col">Action</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><div style="display:flex;align-items:center;gap:.5rem;"><div class="avatar avatar-sm">AB</div>Ahmad Budi</div></td>
<td>Developer</td>
<td><span class="badge rounded-pill bg-green-subtle">Active</span></td>
<td><button type="button" class="btn btn-outline-primary btn-sm">Edit</button></td>
</tr>
<tr><td>2</td><td>…</td><td>Designer</td><td>…</td><td>…</td></tr>
<tr><td>3</td><td>…</td><td>Manager</td><td>…</td><td>…</td></tr>
</tbody>
</table>
</div>Enhanced — mobile-first toolbar (search full-width, action buttons in a grid). Desktop: search left, actions right.
<!-- Datatable: enable via data-table-* attributes (requires SuhuFramework.init()) -->
<div class="table-datatable rounded"
data-table-search
data-table-export
data-table-refresh
data-table-pagination="4"
data-table-count
data-table-search-placeholder="Search name, role, status…"
data-table-export-label="Ekspor .xlsx"
data-table-refresh-label="Refresh"
data-table-export-filename="daftar-pengguna.xlsx"
data-table-count-template="Showing {start}–{end} of {filtered} entries"
data-table-empty="No matching records found.">
<div class="table-responsive">
<table class="table rounded table-striped table-hover">
<thead><tr><th scope="col">#</th><th scope="col">Name</th><th scope="col">Role</th><th scope="col">Status</th><th scope="col">Action</th></tr></thead>
<tbody>
<!-- 8 data rows like preview -->
<tr><td>1</td><td>…</td><td>Developer</td><td>…</td><td>…</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Table modifiers -->
<table class="table rounded table-bordered"></table>
<table class="table rounded table-dark"></table>Breadcrumb
Path navigation with auto-generated / dividers via CSS.
<!-- Breadcrumb: <nav> + <ol class="breadcrumb"> -->
<nav>
<ol class="breadcrumb rounded">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item"><a href="#">Section</a></li>
<li class="breadcrumb-item active">Current</li>
</ol>
</nav>Pagination
Page navigation with active, disabled, and size variants.
<!-- Pagination: .page-item + .active / .disabled -->
<nav aria-label="Pagination">
<ul class="pagination">
<li class="page-item disabled"><span class="page-link rounded">‹</span></li>
<li class="page-item"><a class="page-link rounded" href="#">1</a></li>
<li class="page-item active"><a class="page-link rounded" href="#" aria-current="page">2</a></li>
<li class="page-item"><a class="page-link rounded" href="#">›</a></li>
</ul>
</nav>
<!-- Sizes -->
<ul class="pagination pagination-sm">...</ul>
<ul class="pagination pagination-lg">...</ul>Dropdowns
Click-triggered standalone dropdowns. Add .dropdown-end to align right.
<!-- Dropdown: click .dropdown-toggle; JS adds .open -->
<div class="dropdown">
<button type="button" class="btn btn-primary dropdown-toggle">Actions ▾</button>
<div class="dropdown-menu rounded-3">
<div class="dropdown-header">User</div>
<a href="#" class="dropdown-item rounded">Edit Profile</a>
<a href="#" class="dropdown-item rounded">Settings</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item rounded" style="color:#f87171;">Delete Account</a>
</div>
</div>
<div class="dropdown">
<button type="button" class="btn btn-outline-primary dropdown-toggle">Options ▾</button>
<div class="dropdown-menu rounded-3">
<a href="#" class="dropdown-item rounded">Option A</a>
<a href="#" class="dropdown-item rounded">Option B</a>
<a href="#" class="dropdown-item rounded">Option C</a>
</div>
</div>
<!-- Align right: .dropdown-end -->
<div class="dropdown dropdown-end">…</div>Color Palette
Full SuhuFramework color system exposed as CSS variables and utility classes.
Slate
Sky / Blue
Indigo / Purple / Pink
Green / Emerald / Cyan / Amber / Red
/* Override in your stylesheet */
:root {
--primary: #3b82f6;
--success: #22c55e;
--danger: #ef4444;
--warning: #f59e0b;
--info: #06b6d4;
}
/* Background utility classes */
.bg-primary { background: var(--primary); color: #fff; }
.bg-blue-subtle { background: #dbeafe; color: #1e40af; }
.bg-green-subtle { background: #dcfce7; color: #166534; }
/* Text color utilities */
.text-primary { color: #2563eb; }
.text-success { color: #16a34a; }
.text-danger { color: #ef4444; }
.text-muted { color: #94a3b8; }Animations
CSS animation utilities, scroll-triggered reveal effects, and mobile animations (.mobile-fade-left-in, etc.).
<!-- Preview demos -->
<div class="animate-float">…</div>
<button class="btn btn-gradient btn-sm animate-pulse">Pulse</button>
<div class="spinner-border spinner-primary"></div>
<div class="animate-bounce">…</div>
<div class="animate-scale-in">…</div>
<!-- All animation utilities -->
<div class="animate-float"></div>
<div class="animate-bounce"></div>
<div class="animate-pulse"></div>
<div class="animate-spin"></div>
<div class="animate-fade-in-up"></div>
<div class="animate-scale-in"></div>
<!-- Scroll reveal -->
<div class="reveal"></div>
<div class="reveal-left"></div>
<div class="reveal-right delay-200"></div>
<!-- Mobile animations (see Mobile Animations section) -->
<div class="mobile-fade-left-in"></div>
<div class="mobile-fade-right-out"></div>
<div class="mobile-scale-in"></div>
<!-- Counter (hero stats) -->
<span data-count="50" data-suffix="+">0</span>Background
Background color, gradient, and transparent utilities compatible with SuhuFramework utility naming.
<div class="bg-primary">Primary</div>
<div class="bg-secondary">Secondary</div>
<div class="bg-success">Success</div>
<div class="bg-danger">Danger</div>
<div class="bg-warning">Warning</div>
<div class="bg-info">Info</div>
<div class="bg-dark">Dark</div>
<div class="bg-white">White</div>
<div class="bg-transparent">Transparent</div>
<!-- Gradient backgrounds -->
<div class="bg-gradient-primary">Gradient Primary</div>
<div class="bg-gradient-purple">Gradient Purple</div>
<div class="bg-gradient-success">Gradient Success</div>
<div class="bg-gradient-danger">Gradient Danger</div>
<!-- Subtle backgrounds -->
<div class="bg-blue-subtle">Blue Subtle</div>
<div class="bg-green-subtle">Green Subtle</div>
<div class="bg-red-subtle">Red Subtle</div>
<div class="bg-amber-subtle">Amber Subtle</div>
<div class="bg-transparent">Transparent</div>Borders
Border, border-color, and border-radius utilities for quickly styling elements.
<!-- Border sides -->
<div class="border">All sides</div>
<div class="border-0">No border</div>
<div class="border-top">Top only</div>
<div class="border-bottom">Bottom only</div>
<div class="border-start">Left only</div>
<div class="border-end">Right only</div>
<!-- Border colors -->
<div class="border border-primary">Primary</div>
<div class="border border-success">Success</div>
<div class="border border-danger">Danger</div>
<!-- Border radius -->
<div class="rounded-0">No radius</div>
<div class="rounded">Default</div>
<div class="rounded-pill">Pill</div>
<div class="rounded-circle">Circle</div>Colors
Text color utilities using text-* classes. Covers semantic colors and gradients.
<span class="text-primary">Primary</span>
<span class="text-secondary">Secondary</span>
<span class="text-success">Success</span>
<span class="text-danger">Danger</span>
<span class="text-warning">Warning</span>
<span class="text-info">Info</span>
<span class="text-white">White</span>
<span class="text-dark">Dark</span>
<span class="text-muted">Muted</span>
<span class="text-body">Body</span>
<span class="text-reset">Reset (inherit)</span>
<!-- Gradient text -->
<span class="text-gradient">Default gradient</span>
<span class="text-gradient-blue">Blue gradient</span>
<span class="text-gradient-purple">Purple gradient</span>
<span class="text-gradient-cyan">Cyan gradient</span>Display
Control element display with d-* classes. Responsive variants available: d-sm-*, d-md-*, d-lg-*, d-xl-*.
.d-block
.d-inline
.d-inline-block
.d-flex
.d-inline-flex
.d-grid
.d-table
.d-none
.d-md-none
.d-lg-block
<div class="d-block">Block</div>
<span class="d-inline">Inline</span>
<div class="d-inline-block">Inline block</div>
<div class="d-flex">Flex</div>
<div class="d-grid">Grid</div>
<div class="d-none">Hidden</div>
<!-- Responsive display -->
<div class="d-none d-md-block">Visible on md+</div>
<div class="d-md-none">Hidden on md+</div>
<div class="d-none d-lg-flex">Flex on lg+</div>Flex
Comprehensive flexbox utilities for direction, wrapping, justification, alignment, and gaps.
<!-- Direction -->
<div class="d-flex flex-row">...</div>
<div class="d-flex flex-column">...</div>
<!-- Justify -->
<div class="d-flex justify-content-start">...</div>
<div class="d-flex justify-content-center">...</div>
<div class="d-flex justify-content-end">...</div>
<div class="d-flex justify-content-between">...</div>
<div class="d-flex justify-content-around">...</div>
<div class="d-flex justify-content-evenly">...</div>
<!-- Align -->
<div class="d-flex align-items-start">...</div>
<div class="d-flex align-items-center">...</div>
<div class="d-flex align-items-end">...</div>
<div class="d-flex align-items-stretch">...</div>
<!-- Wrap & grow -->
<div class="d-flex flex-wrap gap-2">...</div>
<div class="d-flex flex-nowrap">...</div>
<div class="d-flex"><div class="flex-fill">Fill</div></div>
<div class="d-flex"><div class="flex-grow-1">Grow</div></div>
<!-- Self -->
<div class="align-self-center">Centered self</div>Float
Float an element left or right. Always use .clearfix on the parent to contain floated children.
<div class="clearfix">
<div class="float-start">Float left</div>
<div class="float-end">Float right</div>
</div>
<!-- Remove float -->
<div class="float-none">No float</div>Interactions
Control user interactions: user-select and pointer-events utilities.
This text cannot be selected (.user-select-none)
Click once to select all (.user-select-all)
<!-- user-select -->
<p class="user-select-none">Cannot be selected</p>
<p class="user-select-all">All selected on click</p>
<p class="user-select-auto">Default selection</p>
<!-- pointer-events -->
<a href="#" class="pe-none">Link disabled</a>
<a href="#" class="pe-auto">Link active</a>Link
Styled link color utilities with link-* classes and underline offset controls. Link hover has no underline by default.
<a href="#" class="link-primary">Primary link</a>
<a href="#" class="link-secondary">Secondary link</a>
<a href="#" class="link-success">Success link</a>
<a href="#" class="link-danger">Danger link</a>
<a href="#" class="link-body">Body colour</a>
<!-- Underline offset -->
<a href="#" class="link-underline link-offset-1">Offset 1</a>
<a href="#" class="link-underline link-offset-2">Offset 2</a>
<a href="#" class="link-underline link-offset-3">Offset 3</a>
<!-- No underline -->
<a href="#" class="text-decoration-none">No underline</a>Object fit
Control how an image or video is resized to fit its container with object-fit-* classes.
.object-fit-cover
.object-fit-contain
.object-fit-fill
.object-fit-scale
<img class="object-fit-cover w-100 h-100" src="..." alt="">
<img class="object-fit-contain w-100 h-100" src="..." alt="">
<img class="object-fit-fill w-100 h-100" src="..." alt="">
<img class="object-fit-scale w-100 h-100" src="..." alt="">
<img class="object-fit-none w-100 h-100" src="..." alt="">Overflow
Control content overflow on both axes with overflow-*, overflow-x-*, and overflow-y-*.
overflow-hidden
overflow-auto
overflow-x-auto
<div class="overflow-auto">Scrollable</div>
<div class="overflow-hidden">Clipped</div>
<div class="overflow-scroll">Always scroll</div>
<div class="overflow-visible">Visible</div>
<div class="overflow-x-auto">Scroll X only</div>
<div class="overflow-y-auto">Scroll Y only</div>
<div class="overflow-x-hidden">Hide X overflow</div>
<div class="overflow-y-hidden">Hide Y overflow</div>Opacity
Set element opacity with opacity-0, opacity-25, opacity-50, opacity-75, opacity-100.
opacity-100
opacity-75
opacity-50
opacity-25
opacity-0
<div class="opacity-0">Invisible (opacity 0)</div>
<div class="opacity-25">25% opacity</div>
<div class="opacity-50">50% opacity</div>
<div class="opacity-75">75% opacity</div>
<div class="opacity-100">Full opacity</div>Position
Position utilities: position-*, top-*, start-*, end-*, bottom-*, and centering helpers.
.position-static
.position-relative
.position-absolute
.position-fixed
.position-sticky
<div class="position-relative">
<span class="position-absolute top-0 start-0">Top left</span>
<span class="position-absolute top-0 end-0">Top right</span>
<span class="position-absolute bottom-0 start-0">Bottom left</span>
<span class="position-absolute bottom-0 end-0">Bottom right</span>
<span class="position-absolute top-50 start-50 translate-middle">Center</span>
</div>
<!-- Position types -->
<div class="position-static">Static</div>
<div class="position-relative">Relative</div>
<div class="position-sticky top-0">Sticky top</div>Shadows
Box shadow scale from shadow-none to shadow-xl.
shadow-none
shadow-sm
shadow
shadow-md
shadow-lg
shadow-xl
<div class="shadow-none">No shadow</div>
<div class="shadow-sm">Small shadow</div>
<div class="shadow">Default shadow</div>
<div class="shadow-md">Medium shadow</div>
<div class="shadow-lg">Large shadow</div>
<div class="shadow-xl">Extra large shadow</div>Sizing
Width and height utilities: w-*, h-*, mw-100, mh-100, vw-100, vh-100.
<!-- Width -->
<div class="w-25">25%</div>
<div class="w-50">50%</div>
<div class="w-75">75%</div>
<div class="w-100">100%</div>
<div class="w-auto">Auto</div>
<!-- Height -->
<div class="h-25">25%</div>
<div class="h-50">50%</div>
<div class="h-100">100%</div>
<div class="h-auto">Auto</div>
<!-- Max -->
<img class="mw-100" src="..." alt="">
<div class="mh-100">Max height 100%</div>
<!-- Viewport -->
<div class="vw-100">Full viewport width</div>
<div class="vh-100">Full viewport height</div>
<div class="min-vh-100">Min full height</div>Spacing
Margin (m-*, mt-*, mb-*, ms-*, me-*, mx-*, my-*) and Padding (p-*, etc.) utilities. Scale: 0–5.
<!-- Margin (0=0, 1=.25rem, 2=.5rem, 3=1rem, 4=1.5rem, 5=3rem) -->
<div class="m-3">margin all 1rem</div>
<div class="mt-3">margin-top</div>
<div class="mb-3">margin-bottom</div>
<div class="ms-3">margin-left</div>
<div class="me-3">margin-right</div>
<div class="mx-3">margin horizontal</div>
<div class="my-3">margin vertical</div>
<div class="mx-auto">center block</div>
<!-- Padding -->
<div class="p-3">padding all 1rem</div>
<div class="pt-3">padding-top</div>
<div class="px-4 py-2">pill padding</div>
<!-- Gap (for flex/grid) -->
<div class="d-flex gap-2">gap .5rem</div>
<div class="d-flex gap-4">gap 1.5rem</div>Text
Alignment, wrapping, transform, weight, style, size, and decoration utilities for typography.
.text-start — left aligned
.text-center — center aligned
.text-end — right aligned
.text-truncate — this text is very long and will be truncated with an ellipsis at the end.
.text-nowrap — will not wrap to a second line even if it is long
<!-- Alignment -->
<p class="text-start">Left</p>
<p class="text-center">Center</p>
<p class="text-end">Right</p>
<!-- Weight -->
<span class="fw-light">Light (300)</span>
<span class="fw-normal">Normal (400)</span>
<span class="fw-semibold">Semibold (600)</span>
<span class="fw-bold">Bold (700)</span>
<!-- Style -->
<span class="fst-italic">Italic</span>
<span class="fst-normal">Normal style</span>
<!-- Transform -->
<span class="text-uppercase">uppercase</span>
<span class="text-lowercase">lowercase</span>
<span class="text-capitalize">capitalize</span>
<!-- Decoration -->
<a class="text-decoration-none">No underline</a>
<a class="text-decoration-underline">Underline</a>
<span class="text-decoration-line-through">Strikethrough</span>
<!-- Wrap / Truncate -->
<p class="text-nowrap">No wrap</p>
<p class="text-wrap">Normal wrap</p>
<p class="text-truncate">Truncate long text...</p>
<p class="text-break">Force break-word</p>
<!-- Font sizes -->
<span class="fs-1">fs-1 (2.5rem)</span>
<span class="fs-3">fs-3 (1.75rem)</span>
<span class="fs-6">fs-6 (1rem)</span>
<span class="fs-sm">fs-sm (.875rem)</span>
<span class="fs-xs">fs-xs (.75rem)</span>Vertical align
Vertical alignment utilities for inline and table-cell elements.
<span class="align-baseline">baseline</span>
<span class="align-top">top</span>
<span class="align-middle">middle</span>
<span class="align-bottom">bottom</span>
<span class="align-text-top">text-top</span>
<span class="align-text-bottom">text-bottom</span>Visibility
.visible and .invisible toggle visibility without affecting layout (unlike d-none).
.visible
.invisible
(space preserved)
<!-- Still takes up space, just not visible -->
<div class="visible">Visible</div>
<div class="invisible">Hidden but layout preserved</div>
<!-- To remove from layout completely, use d-none -->
<div class="d-none">Removed from layout</div>Z-index
Z-index scale utilities: z-n1, z-0, z-1, z-2, z-3.
<div class="position-relative">
<div class="position-absolute z-n1">Behind (-1)</div>
<div class="position-absolute z-0">Layer 0</div>
<div class="position-absolute z-1">Layer 1</div>
<div class="position-absolute z-2">Layer 2</div>
<div class="position-absolute z-3">Top layer (3)</div>
</div>Collapse
Toggle visibility of content with data-bs-toggle="collapse" and data-bs-target="#id". Use data-bs-parent for accordion behavior.
Accordion (one open at a time)
<!-- Collapse: data-bs-toggle + data-bs-target to #id panel -->
<button type="button" class="btn btn-primary"
data-bs-toggle="collapse"
data-bs-target="#collapse-demo-1"
aria-expanded="false">
Toggle content
</button>
<div id="collapse-demo-1" class="collapse">
This content is now visible! Click the button again to hide it.
</div>
<!-- Accordion: data-bs-parent so only one panel stays open -->
<div class="accordion" id="collapse-parent">
<div class="accordion-item">
<div class="accordion-header">
<button type="button" class="accordion-button" aria-expanded="false">
Section 1
<svg class="accordion-icon" …></svg>
</button>
</div>
<div id="collapse-acc-1" class="accordion-collapse collapse" data-bs-parent="#collapse-parent">
<div class="accordion-body">Content for section 1.</div>
</div>
</div>
<div class="accordion-item">
<div class="accordion-header">
<button type="button" class="accordion-button" aria-expanded="false">Section 2 …</button>
</div>
<div id="collapse-acc-2" class="accordion-collapse collapse" data-bs-parent="#collapse-parent">
<div class="accordion-body">Content for section 2.</div>
</div>
</div>
</div>List group
Flexible component for displaying lists of content with optional active states, colours, and action variants.
Default
- Active item
- Normal item
- Another item
- Fourth item
- Disabled
Action links
Contextual
- Primary
- Success
- Danger
- Warning
- Secondary
<!-- List group default -->
<ul class="list-group">
<li class="list-group-item active">Active item</li>
<li class="list-group-item">Normal item</li>
<li class="list-group-item">Another item</li>
<li class="list-group-item">Fourth item</li>
<li class="list-group-item disabled">Disabled</li>
</ul>
<!-- Clickable link -->
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action">Dashboard</a>
<a href="#" class="list-group-item list-group-item-action active">Components</a>
<a href="#" class="list-group-item list-group-item-action">Settings</a>
<a href="#" class="list-group-item list-group-item-action">Profile</a>
<a href="#" class="list-group-item list-group-item-action">Help</a>
</div>
<!-- Contextual colors -->
<ul class="list-group">
<li class="list-group-item list-group-item-primary">Primary</li>
<li class="list-group-item list-group-item-success">Success</li>
<li class="list-group-item list-group-item-danger">Danger</li>
<li class="list-group-item list-group-item-warning">Warning</li>
<li class="list-group-item list-group-item-secondary">Secondary</li>
</ul>Scrollspy
Automatically update navigation links based on scroll position. Set data-bs-spy="scroll" on the scrollable container and data-bs-target to your nav's selector.
Section 1
Scroll inside this panel — the section links above update as each heading passes the top edge.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
Section 2
The active nav link uses the .active class. Set data-bs-offset to match padding or a sticky header inside the scroll area.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget.
Tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.
Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi.
Section 3
Keep scrolling until this block — Section 3 should stay highlighted at the bottom of the panel.
Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis.
Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat.
Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus. Phasellus ultrices nulla quis nibh. Quisque a odio. Morbi non lectus.
<!-- Scrollspy: data-bs-spy on scroll panel + data-bs-target to nav -->
<div class="sf-scrollspy-demo">
<nav id="spy-nav-demo" class="sf-scrollspy-nav">
<p class="sf-scrollspy-nav-label">On this page</p>
<ul class="sf-scrollspy-nav-list">
<li><a href="#spy-sec-1" class="sf-sidebar-link">Section 1</a></li>
<li><a href="#spy-sec-2" class="sf-sidebar-link">Section 2</a></li>
<li><a href="#spy-sec-3" class="sf-sidebar-link">Section 3</a></li>
</ul>
</nav>
<div data-bs-spy="scroll"
data-bs-target="#spy-nav-demo"
data-bs-offset="0"
class="sf-scrollspy-panel">
<h3 id="spy-sec-1" class="sf-scrollspy-heading">Section 1</h3>
<p class="sf-scrollspy-text">...</p>
<h3 id="spy-sec-2" class="sf-scrollspy-heading">Section 2</h3>
<p class="sf-scrollspy-text">...</p>
<h3 id="spy-sec-3" class="sf-scrollspy-heading">Section 3</h3>
<p class="sf-scrollspy-text">...</p>
</div>
</div>
<!-- Mobile: nav pills on top, full-width panel (see .sf-scrollspy-demo CSS) -->Input group
Extend form controls with prepended or appended text, icons, and buttons using .input-group and .input-group-text.
<!-- Prepend text -->
<div class="input-group">
<span class="input-group-text rounded">@</span>
<input type="text" class="form-control rounded" placeholder="username">
</div>
<!-- Append text -->
<div class="input-group">
<input type="text" class="form-control rounded" placeholder="Amount">
<span class="input-group-text rounded">.00</span>
</div>
<!-- Both sides -->
<div class="input-group">
<span class="input-group-text rounded">$</span>
<input type="number" class="form-control rounded" placeholder="0.00">
<span class="input-group-text rounded">USD</span>
</div>
<!-- With button -->
<div class="input-group">
<input type="text" class="form-control rounded" placeholder="Search...">
<button class="btn btn-primary">Search</button>
</div>
<!-- File upload -->
<div class="input-group input-group-file">
<label class="btn btn-outline-secondary" for="my-file">Browse</label>
<input type="file" id="my-file" class="visually-hidden" tabindex="-1">
<input type="text" class="form-control rounded" readonly placeholder="Choose file..." data-file-display>
</div>Floating labels
Label starts inside the field like a placeholder; on focus or when filled it animates up to the top border. For <select>, use an empty hidden first <option> so the label is the only placeholder text.
<!-- Text input — placeholder=" " required for float detection -->
<div class="form-floating">
<input type="email" class="form-control rounded"
id="emailInput" placeholder=" ">
<label for="emailInput">Email address</label>
</div>
<!-- Password -->
<div class="form-floating">
<input type="password" class="form-control rounded"
id="passInput" placeholder=" ">
<label for="passInput">Password</label>
</div>
<!-- Select — empty hidden first option; label is the placeholder -->
<div class="form-floating">
<select class="form-select rounded" id="mySelect">
<option value="" selected disabled hidden></option>
<option value="1">One</option>
</select>
<label for="mySelect">Select option</label>
</div>Validation
Show valid/invalid states using .is-valid / .is-invalid classes, or add .was-validated to the form to use native HTML5 constraint API.
<!-- Manual validation: .is-valid / .is-invalid + .valid-feedback / .invalid-feedback -->
<form novalidate>
<div>
<label class="form-label" for="val-email">Email address</label>
<input type="email" class="form-control rounded is-valid" id="val-email" value="hello@suhu.dev">
<div class="valid-feedback">Looks good!</div>
</div>
<div>
<label class="form-label" for="val-pass">Password</label>
<input type="password" class="form-control rounded is-invalid" id="val-pass" value="">
<div class="invalid-feedback">Password is required (min. 8 characters).</div>
</div>
<div>
<label class="form-label" for="val-city">City</label>
<select class="form-select rounded is-invalid" id="val-city">
<option value="">Choose a city...</option>
<option>Jakarta</option>
<option>Bandung</option>
</select>
<div class="invalid-feedback">Please select a valid city.</div>
</div>
<button type="submit" class="btn btn-primary">Submit form</button>
</form>
<!-- Or HTML5 validation: add .was-validated to <form> -->