Building a Responsive Card Grid in Bulma Without Writing Custom CSS
Bulma's responsive column modifiers let you build a three-up card grid that collapses on mobile with zero custom CSS — here's the markup, the breakpoint model, and the CSS-only trade-off.
07 Jan 2026, 21:26 UTC

You need a row of cards — say, a product listing or a team directory — that shows three across on a desktop monitor, two across on a tablet, and stacks vertically on a phone. The classic way to do this is a handful of media queries and some flexbox math. Bulma's answer is more interesting: one container class and one sizing class per column, and the framework's breakpoint system does the rest. No custom stylesheet required.
The thesis here is simple: Bulma's responsive column modifiers are the highest-leverage feature in the framework, and they work precisely because Bulma refuses to ship any JavaScript. Understanding that trade-off — pure CSS in, your own JS for interactivity — is the key to deciding whether Bulma fits your project.
How the columns system actually works
Bulma's grid is built on flexbox. A .columns container holds any number of .column children, and by default each child grows to fill an equal share of the row. Three .column divs, three equal columns. That alone covers a surprising number of layouts.
When you need proportional control, you add a size class: .is-one-third, .is-half, .is-4 (Bulma also accepts a 12-column numeric scale). The remaining columns split whatever space is left. Still no media queries.
The responsive part comes from breakpoint suffixes. A class like .is-one-third-desktop means "one third wide, but only at the desktop breakpoint and up." Below that breakpoint the column falls back to full width, which is exactly the stacking behavior you want on a phone. Bulma's breakpoints are fixed: mobile up to 768px, tablet from 769px, desktop from 1024px, widescreen from 1216px, and fullhd from 1408px. Because these are defined in the framework's variables, your layout breakpoints stay consistent across every component instead of drifting between hand-picked pixel values.
The worked example: a three-up card grid
Here is the complete markup for the grid described at the top. Save it as an HTML file, link the Bulma CDN build in the <head>, and open it in a browser:
<section class="section">
<div class="container">
<div class="columns is-multiline">
<div class="column is-one-third-desktop is-half-tablet">
<div class="card">
<div class="card-content">Card one</div>
</div>
</div>
<div class="column is-one-third-desktop is-half-tablet">
<div class="card">
<div class="card-content">Card two</div>
</div>
</div>
<div class="column is-one-third-desktop is-half-tablet">
<div class="card">
<div class="card-content">Card three</div>
</div>
</div>
</div>
</div>
</section>Three classes do all the layout work:
is-multilineon the container lets columns wrap to a new row instead of being squeezed onto one — essential once you have more cards than fit per row.is-one-third-desktopgives you three across at 1024px and wider.is-half-tabletgives you two across from 769px to 1023px.- Below 769px, no size class applies, so each column takes the full row and the cards stack.
To verify it, resize the browser window (or use devtools' device toolbar) and watch the layout change as you cross 768px and 1024px. If the grid doesn't respond, the first thing to check is that the Bulma stylesheet is actually loading — open devtools' Network tab and confirm the CSS request returned 200.
The trade-off: CSS-only means you own the JavaScript
Bulma deliberately ships zero JavaScript. That keeps the framework small and means it pairs cleanly with React, Vue, or Svelte — your components just render Bulma class names, and there's no CSS-in-JS conflict to untangle. But it also means anything interactive is your responsibility. The most common beginner trap is copying the navbar markup and discovering the hamburger menu does nothing on mobile.
The fix is a few lines: the burger works by toggling the is-active class on both the .navbar-burger button and the matching .navbar-menu element. A minimal toggle looks like this:
document.querySelectorAll('.navbar-burger').forEach(burger => {
burger.addEventListener('click', () => {
const target = document.getElementById(burger.dataset.target);
burger.classList.toggle('is-active');
target.classList.toggle('is-active');
});
});Run this after the DOM loads (a <script> at the end of <body> is fine for a static page). The same pattern applies to modals, tabs, and dropdowns: Bulma styles the states, you flip the classes.
Limitations worth knowing before you commit
First, the class strings get long. column is-one-third-desktop is-half-tablet repeated across twelve cards is verbose, and in a real app you'll want to extract a reusable CardColumn component rather than paste the string everywhere.
Second, the columns system predates CSS Grid. It's a one-dimensional tool: great for rows of items, awkward for genuinely two-dimensional layouts like a dashboard with a spanning sidebar. If your design needs that, native CSS Grid is often simpler than nesting Bulma columns — and the two coexist fine in one project.
Third, check your version before following tutorials. Bulma 1.x reworked the internals — CSS custom properties (the --bulma-* variables), a modernized Sass module setup, and built-in dark-mode support — so advice written for 0.9.x can mislead you on theming even when the layout classes still work. Check the version in your package.json or the comment header of the compiled CSS, and confirm the --bulma- custom properties exist in devtools before relying on them for customization.
Where to start
Drop the CDN link into a scratch HTML file, paste the card grid above, and drag the viewport across the 768px and 1024px boundaries. That five-minute experiment tells you most of what you need: whether the breakpoint model matches your design, and whether you're comfortable owning the JavaScript for the interactive parts. If both answers are yes, Bulma's column system will carry a lot of layout weight for very little code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.