NEW Browse AI tools across categories — updated daily. See what's new →

Go Bulma

Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero-dependency binaries. Internal skill used by golang-pro agent when building HTML UIs with a classes-based CSS framewo...

Authorsgaunet
Version1.0.0
LicenseMIT
Token count~3,972
UpdatedJun 5, 2026

Install

Quick install

via npx skills · works with 57+ agents
npx skills add https://github.com/jgthms/bulma
Or pick agent:
npx skills add jgthms/bulma --agent claude-code
npx skills add jgthms/bulma --agent cursor
npx skills add jgthms/bulma --agent codex
npx skills add jgthms/bulma --agent opencode
npx skills add jgthms/bulma --agent github-copilot
npx skills add jgthms/bulma --agent windsurf
More install options

Shorthand — useful for multi-skill repos:

npx skills add jgthms/bulma

Manual — clone the repo and drop the folder into your agent's skills directory:

git clone https://github.com/jgthms/bulma.git
cp -r bulma ~/.claude/skills/
How to use: Once installed, ask your agent to "use the Go Bulma skill" or describe what you want (e.g. "Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero"). Requires Node.js 18+.

Go Bulma

Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero-dependency binaries. Internal skill used by golang-pro agent when building HTML UIs with a classes-based CSS framework and no Node toolchain.

---
name: go-bulma
description: Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero-dependency binaries. Internal skill used by golang-pro agent when building HTML UIs with a classes-based CSS framework and no Node toolchain.
user-invocable: false
allowed-tools: Read, Write, Edit, Glob, Grep, Bash(go:), Bash(curl:), Bash(mkdir:), Bash(shasum:), Bash(sha256sum:*)
---

Bulma for Go

Scaffold Bulma — a modern, classes-based, CSS-only framework (MIT, no JavaScript, no jQuery) — into a Go web app. Assets are always downloaded at setup time and embedded into the binary with //go:embed. The running binary never fetches anything from the network.

Bulma styles components through utility and component classes (class="button is-primary", class="column is-half", class="navbar"). Bulma 1.0+ (released Feb 2024) ships CSS custom properties for theming, so colors, radii, fonts, and spacing can be customized without Sass or a Node build step.

Bulma intentionally ships zero JavaScript, so interactive components (navbar burger, modal, dropdown, tabs) need a small amount of vanilla JS to actually work. This skill scaffolds a self-contained app.js covering those patterns.

When to Use

  • User asks for a polished classes-based HTML UI in a Go app without React/Vue/Angular
  • User mentions "Bulma", "no Node", "no Sass", or wants a Bootstrap-like experience with a cleaner class system
  • html/template, templ, or *.gohtml files exist but no CSS framework is present
  • bulma.min.css is already in the repo and needs wiring or upgrading

Prerequisites

  1. go.mod exists: The project must be a Go module. Abort otherwise.
  2. Go ≥ 1.16: //go:embed requires Go 1.16+. Read the go directive in go.mod to verify.
  3. HTTP server present or planned: Bulma renders HTML served over HTTP. Confirm with the user if unclear.

Core Principle

Zero runtime dependencies. The binary must run offline after go build. Never reference cdn.jsdelivr.net, unpkg.com, or cdnjs.cloudflare.com from rendered HTML. Download once at setup time, embed, commit, ship.

Asset Source

Use the pinned npm-on-jsdelivr URL — it is immutable per version and fetched only at setup:

https://cdn.jsdelivr.net/npm/bulma@<VERSION>/css/bulma.min.css

Alternative (also acceptable, also pinned): the GitHub release zip:

https://github.com/jgthms/bulma/releases/download/<VERSION>/bulma-<VERSION>.zip

The zip contains css/bulma.min.css and the unminified sources. For a Go workflow the single bulma.min.css from jsdelivr is simpler.

Do not use:


  • unpkg.com/bulma/... without a version — resolves to latest, not reproducible

  • cdnjs.cloudflare.com/.../latest/... — same problem

  • Bulma's GitHub master branch raw URLs — moving target, not a release artifact

To find the current version: curl -s https://api.github.com/repos/jgthms/bulma/releases/latest | grep tag_name

Workflow: Scaffold Bulma into a Go project

Step 1: Choose asset layout

| Project shape | Recommended layout |
|---|---|
| Flat app (main.go at root) | web/static/bulma.min.css, web/static/app.js, web/embed.go |
| internal/ + layered (cmd/..., internal/...) | internal/ui/static/bulma.min.css, internal/ui/embed.go |
| Library with optional web UI | web/ui/static/... in a separate package |

Prefer internal/ui/ when there is already an internal/ tree. Otherwise use web/.

Step 2: Download pinned assets

VERSION=1.0.4  # confirm latest via the release API before running
DEST=internal/ui/static
mkdir -p "$DEST"
curl -sSLo "$DEST/bulma.min.css" "https://cdn.jsdelivr.net/npm/bulma@${VERSION}/css/bulma.min.css"

The jsdelivr npm path uses a bare version like 1.0.4 (no v prefix); GitHub release tags happen to match (also 1.0.4, no v).

Step 3: Record the version and checksums

Create a BULMA_VERSION file (or header comment in embed.go) so upgrades are traceable:

bulma 1.0.4
bulma.min.css sha256: <output of shasum -a 256 bulma.min.css>
app.js        sha256: <output of shasum -a 256 app.js>

Step 4: Create the embed package

// internal/ui/embed.go
package ui

import "embed"

// Static holds Bulma's CSS and the vanilla JS helpers.
// See ../../BULMA_VERSION for the pinned release.
//
//go:embed static/bulma.min.css static/app.js
var Static embed.FS

If a theme.css is added later (see Theming), extend the directive:

//go:embed static/bulma.min.css static/app.js static/theme.css

Step 5: Wire the static file handler

// in route setup (main.go or internal/server/routes.go)
import (
    "io/fs"
    "net/http"

    "yourmod/internal/ui"
)

func registerStatic(mux *http.ServeMux) {
    sub, err := fs.Sub(ui.Static, "static")
    if err != nil {
        panic(err) // compile-time constant; impossible at runtime
    }
    mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub))))
}

Step 6: Create a minimal Bulma-styled template

<!-- internal/ui/templates/index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{{.Title}}</title>
  <link rel="stylesheet" href="/static/bulma.min.css">
  <script src="/static/app.js" defer></script>
</head>
<body>
  <nav class="navbar is-primary" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item" href="/"><strong>{{.Title}}</strong></a>
      <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="mainMenu">
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
      </a>
    </div>
    <div id="mainMenu" class="navbar-menu">
      <div class="navbar-end">
        <a class="navbar-item" href="/about">About</a>
      </div>
    </div>
  </nav>

  <section class="section">
    <div class="container">
      <h1 class="title">{{.Title}}</h1>
      <form method="post" action="/save">
        <div class="field">
          <label class="label">Name</label>
          <div class="control">
            <input class="input" name="name" required>
          </div>
        </div>
        <div class="field">
          <label class="label">Email</label>
          <div class="control">
            <input class="input" type="email" name="email">
          </div>
        </div>
        <div class="field">
          <div class="control">
            <button class="button is-primary" type="submit">Save</button>
            <button class="button" type="button" data-target="aboutModal">About</button>
          </div>
        </div>
      </form>
    </div>
  </section>

  <div id="aboutModal" class="modal">
    <div class="modal-background"></div>
    <div class="modal-card">
      <header class="modal-card-head">
        <p class="modal-card-title">About</p>
        <button class="delete" aria-label="close"></button>
      </header>
      <section class="modal-card-body">
        <p>Styled by Bulma — embedded in the binary, served offline.</p>
      </section>
    </div>
  </div>
</body>
</html>

Embed templates alongside assets:

//go:embed templates/*.html
var Templates embed.FS

Parse at startup with template.ParseFS(ui.Templates, "templates/*.html").

Step 7: Verify zero-dependency build

go build -o app ./...
./app &
curl -sI http://localhost:PORT/static/bulma.min.css | head -1   # expect 200
curl -sI http://localhost:PORT/static/app.js | head -1          # expect 200
# kill the app, disable network, rerun — it still works

Workflow: Upgrade Bulma

  1. Check the latest release: curl -s https://api.github.com/repos/jgthms/bulma/releases/latest | grep tag_name
  2. Re-run Step 2 with the new version
  3. Update BULMA_VERSION and checksums (Step 3)
  4. Run the app; review the Bulma changelog for breaking changes (Bulma 1.x is stable; minor releases are additive but always read the changelog)
  5. Commit as a single changeset so the bump is traceable

Pattern: Vanilla JS helpers (app.js)

Write this verbatim to static/app.js. It covers navbar burger, modal open/close, dropdown toggle, and tabs — Bulma's four interactive patterns. Zero dependencies, plain DOM API.

// internal/ui/static/app.js
// Vanilla JS helpers for Bulma's interactive components.
// Bulma ships zero JS by design; this file wires the standard idioms
// documented at https://bulma.io/documentation/.

(() => {
  // Navbar burger: toggle .is-active on burger + matching menu.
  document.querySelectorAll('.navbar-burger').forEach((burger) => {
    burger.addEventListener('click', () => {
      const target = document.getElementById(burger.dataset.target);
      burger.classList.toggle('is-active');
      if (target) target.classList.toggle('is-active');
    });
  });

  // Modal: any element with data-target="modal-id" opens that modal.
  // .modal-background, .modal-close, .delete (inside .modal), and Escape close it.
  const openModal = (el) => el.classList.add('is-active');
  const closeModal = (el) => el.classList.remove('is-active');
  const closeAllModals = () =>
    document.querySelectorAll('.modal.is-active').forEach(closeModal);

  document.querySelectorAll('[data-target]').forEach((trigger) => {
    const target = document.getElementById(trigger.dataset.target);
    if (target && target.classList.contains('modal')) {
      trigger.addEventListener('click', () => openModal(target));
    }
  });

  document
    .querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal .delete')
    .forEach((el) => {
      const modal = el.closest('.modal');
      if (modal) el.addEventListener('click', () => closeModal(modal));
    });

  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') closeAllModals();
  });

  // Dropdown: click .dropdown-trigger to toggle, click outside to close.
  document.querySelectorAll('.dropdown:not(.is-hoverable)').forEach((dropdown) => {
    const trigger = dropdown.querySelector('.dropdown-trigger');
    if (!trigger) return;
    trigger.addEventListener('click', (e) => {
      e.stopPropagation();
      dropdown.classList.toggle('is-active');
    });
  });
  document.addEventListener('click', () => {
    document
      .querySelectorAll('.dropdown.is-active:not(.is-hoverable)')
      .forEach((d) => d.classList.remove('is-active'));
  });

  // Tabs: <div class="tabs"><ul><li data-tab="panel-id">...</li></ul></div>
  // Panels are any elements with id matching data-tab; only the active one shows.
  document.querySelectorAll('.tabs').forEach((tabs) => {
    const items = tabs.querySelectorAll('li[data-tab]');
    items.forEach((item) => {
      item.addEventListener('click', () => {
        items.forEach((i) => i.classList.remove('is-active'));
        item.classList.add('is-active');
        items.forEach((i) => {
          const panel = document.getElementById(i.dataset.tab);
          if (panel) panel.hidden = i !== item;
        });
      });
    });
  });
})();

Pattern: Pair with templ

If **/*.templ files exist, render via templ components instead of html/template. The embed and handler steps are unchanged — only the rendering layer differs.

// internal/ui/layout.templ
package ui

templ Layout(title string) {
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>{ title }</title>
        <link rel="stylesheet" href="/static/bulma.min.css">
        <script src="/static/app.js" defer></script>
    </head>
    <body>
        <section class="section">
            <div class="container">
                { children... }
            </div>
        </section>
    </body>
    </html>
}

If templ is not yet set up in the project, defer to the go-tool skill to add it via go get -tool github.com/a-h/templ/cmd/templ, then return here.

Pattern: Theming via CSS custom properties

Bulma 1.0+ exposes its design tokens as CSS custom properties (HSL components for colors, plus radii, fonts, spacing). Override them in a sibling theme.css loaded after bulma.min.css:

/* internal/ui/static/theme.css */
:root {
  --bulma-primary-h: 217;
  --bulma-primary-s: 71%;
  --bulma-primary-l: 53%;

  --bulma-link-h: 217;
  --bulma-link-s: 71%;
  --bulma-link-l: 53%;

  --bulma-radius: 4px;
  --bulma-radius-small: 2px;
  --bulma-radius-large: 6px;

  --bulma-family-primary: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
<link rel="stylesheet" href="/static/bulma.min.css">
<link rel="stylesheet" href="/static/theme.css">

Add theme.css to the //go:embed directive. For the full list of custom properties, search bulma.min.css for --bulma- or see the customize colors docs.

Deeper customization (new component variants, removing unused modules to shrink the bundle) requires Sass and Node — out of scope for this skill. If the user asks for that, push back: the value proposition here is "no Node toolchain", and tree-shaking saves at most ~150KB on an already-cached file.

Anti-Patterns

| Don't | Do |
|---|---|
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma/..."> in rendered HTML | Download once at setup, embed, serve from /static/ |
| npm install bulma + a Sass build step | Pure Go workflow — download bulma.min.css, write app.js, embed, done |
| Shipping unminified bulma.css in the binary | Always use bulma.min.css |
| Inline <style> blocks duplicating Bulma utilities | Use Bulma helper classes: .has-text-centered, .mt-4, .is-flex, etc. |
| Mixing Bootstrap and Bulma classes in the same page | Pick one. For Bootstrap, defer to the html-first-frontend agent |
| Forgetting defer on <script src="/static/app.js"> | defer is required — the helpers query the DOM at load time |
| Reaching for jQuery or Alpine for navbar/modal | The scaffolded app.js already covers Bulma's four interactive patterns |

Reference Layout

A completed setup looks like:

.
├── go.mod
├── main.go
├── BULMA_VERSION
└── internal/
    └── ui/
        ├── embed.go
        ├── render.go
        ├── static/
        │   ├── bulma.min.css
        │   ├── app.js
        │   └── theme.css          (optional)
        └── templates/
            ├── layout.html
            └── index.html

Error Handling

| Condition | Action |
|---|---|
| No go.mod | Abort: "Not a Go module — run go mod init first." |
| Go version < 1.16 | Abort: "//go:embed requires Go 1.16+. Update the go directive in go.mod." |
| curl returns non-200 for bulma.min.css | Verify the version exists on npm: https://www.npmjs.com/package/bulma. The release tag and the npm version should match. |
| User insists on CDN at runtime | Push back: the skill's reason for existing is offline, zero-dep, fully standalone binaries. If they still want CDN, decline and let them wire it manually without this skill. |
| Bulma assets already present in the repo | Skip download; verify checksums against BULMA_VERSION and proceed to wire the handler, write app.js if missing, and add the template. |

---

Source: https://github.com/jgthms/bulma
Author: sgaunet
Discovered via: skillsdirectory.com
Genre: development

SKILL.md source

---
name: Go Bulma
description: Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero-dependency binaries. Internal skill used by golang-pro agent when building HTML UIs with a classes-based CSS framewo...
---

# Go Bulma

Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero-dependency binaries. Internal skill used by golang-pro agent when building HTML UIs with a classes-based CSS framework and no Node toolchain.

---
name: go-bulma
description: Scaffold the Bulma CSS framework into a Go web app with embedded assets for zero-dependency binaries. Internal skill used by golang-pro agent when building HTML UIs with a classes-based CSS framework and no Node toolchain.
user-invocable: false
allowed-tools: Read, Write, Edit, Glob, Grep, Bash(go:*), Bash(curl:*), Bash(mkdir:*), Bash(shasum:*), Bash(sha256sum:*)
---

# Bulma for Go

Scaffold [Bulma](https://bulma.io/documentation/) — a modern, classes-based, CSS-only framework (MIT, no JavaScript, no jQuery) — into a Go web app. Assets are **always** downloaded at setup time and embedded into the binary with `//go:embed`. The running binary never fetches anything from the network.

Bulma styles components through utility and component classes (`class="button is-primary"`, `class="column is-half"`, `class="navbar"`). Bulma 1.0+ (released Feb 2024) ships **CSS custom properties** for theming, so colors, radii, fonts, and spacing can be customized without Sass or a Node build step.

Bulma intentionally ships **zero JavaScript**, so interactive components (navbar burger, modal, dropdown, tabs) need a small amount of vanilla JS to actually work. This skill scaffolds a self-contained `app.js` covering those patterns.

## When to Use

- User asks for a polished classes-based HTML UI in a Go app without React/Vue/Angular
- User mentions "Bulma", "no Node", "no Sass", or wants a Bootstrap-like experience with a cleaner class system
- `html/template`, `templ`, or `*.gohtml` files exist but no CSS framework is present
- `bulma.min.css` is already in the repo and needs wiring or upgrading

## Prerequisites

1. **go.mod exists**: The project must be a Go module. Abort otherwise.
2. **Go ≥ 1.16**: `//go:embed` requires Go 1.16+. Read the `go` directive in `go.mod` to verify.
3. **HTTP server present or planned**: Bulma renders HTML served over HTTP. Confirm with the user if unclear.

## Core Principle

**Zero runtime dependencies.** The binary must run offline after `go build`. Never reference `cdn.jsdelivr.net`, `unpkg.com`, or `cdnjs.cloudflare.com` from rendered HTML. Download once at setup time, embed, commit, ship.

## Asset Source

Use the pinned npm-on-jsdelivr URL — it is immutable per version and fetched only at setup:

```
https://cdn.jsdelivr.net/npm/bulma@<VERSION>/css/bulma.min.css
```

Alternative (also acceptable, also pinned): the GitHub release zip:

```
https://github.com/jgthms/bulma/releases/download/<VERSION>/bulma-<VERSION>.zip
```

The zip contains `css/bulma.min.css` and the unminified sources. For a Go workflow the single `bulma.min.css` from jsdelivr is simpler.

**Do not** use:
- `unpkg.com/bulma/...` without a version — resolves to `latest`, not reproducible
- `cdnjs.cloudflare.com/.../latest/...` — same problem
- Bulma's GitHub `master` branch raw URLs — moving target, not a release artifact

To find the current version: `curl -s https://api.github.com/repos/jgthms/bulma/releases/latest | grep tag_name`

## Workflow: Scaffold Bulma into a Go project

### Step 1: Choose asset layout

| Project shape | Recommended layout |
|---|---|
| Flat app (`main.go` at root) | `web/static/bulma.min.css`, `web/static/app.js`, `web/embed.go` |
| `internal/` + layered (`cmd/...`, `internal/...`) | `internal/ui/static/bulma.min.css`, `internal/ui/embed.go` |
| Library with optional web UI | `web/ui/static/...` in a separate package |

Prefer `internal/ui/` when there is already an `internal/` tree. Otherwise use `web/`.

### Step 2: Download pinned assets

```bash
VERSION=1.0.4  # confirm latest via the release API before running
DEST=internal/ui/static
mkdir -p "$DEST"
curl -sSLo "$DEST/bulma.min.css" "https://cdn.jsdelivr.net/npm/bulma@${VERSION}/css/bulma.min.css"
```

The jsdelivr npm path uses a bare version like `1.0.4` (no `v` prefix); GitHub release tags happen to match (also `1.0.4`, no `v`).

### Step 3: Record the version and checksums

Create a `BULMA_VERSION` file (or header comment in `embed.go`) so upgrades are traceable:

```
bulma 1.0.4
bulma.min.css sha256: <output of shasum -a 256 bulma.min.css>
app.js        sha256: <output of shasum -a 256 app.js>
```

### Step 4: Create the embed package

```go
// internal/ui/embed.go
package ui

import "embed"

// Static holds Bulma's CSS and the vanilla JS helpers.
// See ../../BULMA_VERSION for the pinned release.
//
//go:embed static/bulma.min.css static/app.js
var Static embed.FS
```

If a `theme.css` is added later (see Theming), extend the directive:

```go
//go:embed static/bulma.min.css static/app.js static/theme.css
```

### Step 5: Wire the static file handler

```go
// in route setup (main.go or internal/server/routes.go)
import (
    "io/fs"
    "net/http"

    "yourmod/internal/ui"
)

func registerStatic(mux *http.ServeMux) {
    sub, err := fs.Sub(ui.Static, "static")
    if err != nil {
        panic(err) // compile-time constant; impossible at runtime
    }
    mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub))))
}
```

### Step 6: Create a minimal Bulma-styled template

```html
<!-- internal/ui/templates/index.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{{.Title}}</title>
  <link rel="stylesheet" href="/static/bulma.min.css">
  <script src="/static/app.js" defer></script>
</head>
<body>
  <nav class="navbar is-primary" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item" href="/"><strong>{{.Title}}</strong></a>
      <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="mainMenu">
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
        <span aria-hidden="true"></span>
      </a>
    </div>
    <div id="mainMenu" class="navbar-menu">
      <div class="navbar-end">
        <a class="navbar-item" href="/about">About</a>
      </div>
    </div>
  </nav>

  <section class="section">
    <div class="container">
      <h1 class="title">{{.Title}}</h1>
      <form method="post" action="/save">
        <div class="field">
          <label class="label">Name</label>
          <div class="control">
            <input class="input" name="name" required>
          </div>
        </div>
        <div class="field">
          <label class="label">Email</label>
          <div class="control">
            <input class="input" type="email" name="email">
          </div>
        </div>
        <div class="field">
          <div class="control">
            <button class="button is-primary" type="submit">Save</button>
            <button class="button" type="button" data-target="aboutModal">About</button>
          </div>
        </div>
      </form>
    </div>
  </section>

  <div id="aboutModal" class="modal">
    <div class="modal-background"></div>
    <div class="modal-card">
      <header class="modal-card-head">
        <p class="modal-card-title">About</p>
        <button class="delete" aria-label="close"></button>
      </header>
      <section class="modal-card-body">
        <p>Styled by Bulma — embedded in the binary, served offline.</p>
      </section>
    </div>
  </div>
</body>
</html>
```

Embed templates alongside assets:

```go
//go:embed templates/*.html
var Templates embed.FS
```

Parse at startup with `template.ParseFS(ui.Templates, "templates/*.html")`.

### Step 7: Verify zero-dependency build

```bash
go build -o app ./...
./app &
curl -sI http://localhost:PORT/static/bulma.min.css | head -1   # expect 200
curl -sI http://localhost:PORT/static/app.js | head -1          # expect 200
# kill the app, disable network, rerun — it still works
```

## Workflow: Upgrade Bulma

1. Check the latest release: `curl -s https://api.github.com/repos/jgthms/bulma/releases/latest | grep tag_name`
2. Re-run Step 2 with the new version
3. Update `BULMA_VERSION` and checksums (Step 3)
4. Run the app; review the [Bulma changelog](https://github.com/jgthms/bulma/blob/master/CHANGELOG.md) for breaking changes (Bulma 1.x is stable; minor releases are additive but always read the changelog)
5. Commit as a single changeset so the bump is traceable

## Pattern: Vanilla JS helpers (`app.js`)

Write this verbatim to `static/app.js`. It covers navbar burger, modal open/close, dropdown toggle, and tabs — Bulma's four interactive patterns. Zero dependencies, plain DOM API.

```javascript
// internal/ui/static/app.js
// Vanilla JS helpers for Bulma's interactive components.
// Bulma ships zero JS by design; this file wires the standard idioms
// documented at https://bulma.io/documentation/.

(() => {
  // Navbar burger: toggle .is-active on burger + matching menu.
  document.querySelectorAll('.navbar-burger').forEach((burger) => {
    burger.addEventListener('click', () => {
      const target = document.getElementById(burger.dataset.target);
      burger.classList.toggle('is-active');
      if (target) target.classList.toggle('is-active');
    });
  });

  // Modal: any element with data-target="modal-id" opens that modal.
  // .modal-background, .modal-close, .delete (inside .modal), and Escape close it.
  const openModal = (el) => el.classList.add('is-active');
  const closeModal = (el) => el.classList.remove('is-active');
  const closeAllModals = () =>
    document.querySelectorAll('.modal.is-active').forEach(closeModal);

  document.querySelectorAll('[data-target]').forEach((trigger) => {
    const target = document.getElementById(trigger.dataset.target);
    if (target && target.classList.contains('modal')) {
      trigger.addEventListener('click', () => openModal(target));
    }
  });

  document
    .querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal .delete')
    .forEach((el) => {
      const modal = el.closest('.modal');
      if (modal) el.addEventListener('click', () => closeModal(modal));
    });

  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') closeAllModals();
  });

  // Dropdown: click .dropdown-trigger to toggle, click outside to close.
  document.querySelectorAll('.dropdown:not(.is-hoverable)').forEach((dropdown) => {
    const trigger = dropdown.querySelector('.dropdown-trigger');
    if (!trigger) return;
    trigger.addEventListener('click', (e) => {
      e.stopPropagation();
      dropdown.classList.toggle('is-active');
    });
  });
  document.addEventListener('click', () => {
    document
      .querySelectorAll('.dropdown.is-active:not(.is-hoverable)')
      .forEach((d) => d.classList.remove('is-active'));
  });

  // Tabs: <div class="tabs"><ul><li data-tab="panel-id">...</li></ul></div>
  // Panels are any elements with id matching data-tab; only the active one shows.
  document.querySelectorAll('.tabs').forEach((tabs) => {
    const items = tabs.querySelectorAll('li[data-tab]');
    items.forEach((item) => {
      item.addEventListener('click', () => {
        items.forEach((i) => i.classList.remove('is-active'));
        item.classList.add('is-active');
        items.forEach((i) => {
          const panel = document.getElementById(i.dataset.tab);
          if (panel) panel.hidden = i !== item;
        });
      });
    });
  });
})();
```

## Pattern: Pair with templ

If `**/*.templ` files exist, render via templ components instead of `html/template`. The embed and handler steps are unchanged — only the rendering layer differs.

```go
// internal/ui/layout.templ
package ui

templ Layout(title string) {
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>{ title }</title>
        <link rel="stylesheet" href="/static/bulma.min.css">
        <script src="/static/app.js" defer></script>
    </head>
    <body>
        <section class="section">
            <div class="container">
                { children... }
            </div>
        </section>
    </body>
    </html>
}
```

If templ is not yet set up in the project, defer to the **go-tool** skill to add it via `go get -tool github.com/a-h/templ/cmd/templ`, then return here.

## Pattern: Theming via CSS custom properties

Bulma 1.0+ exposes its design tokens as CSS custom properties (HSL components for colors, plus radii, fonts, spacing). Override them in a sibling `theme.css` loaded **after** `bulma.min.css`:

```css
/* internal/ui/static/theme.css */
:root {
  --bulma-primary-h: 217;
  --bulma-primary-s: 71%;
  --bulma-primary-l: 53%;

  --bulma-link-h: 217;
  --bulma-link-s: 71%;
  --bulma-link-l: 53%;

  --bulma-radius: 4px;
  --bulma-radius-small: 2px;
  --bulma-radius-large: 6px;

  --bulma-family-primary: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
```

```html
<link rel="stylesheet" href="/static/bulma.min.css">
<link rel="stylesheet" href="/static/theme.css">
```

Add `theme.css` to the `//go:embed` directive. For the full list of custom properties, search `bulma.min.css` for `--bulma-` or see the [customize colors docs](https://bulma.io/documentation/features/customize-colors/).

Deeper customization (new component variants, removing unused modules to shrink the bundle) requires Sass and Node — **out of scope for this skill**. If the user asks for that, push back: the value proposition here is "no Node toolchain", and tree-shaking saves at most ~150KB on an already-cached file.

## Anti-Patterns

| Don't | Do |
|---|---|
| `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma/...">` in rendered HTML | Download once at setup, embed, serve from `/static/` |
| `npm install bulma` + a Sass build step | Pure Go workflow — download `bulma.min.css`, write `app.js`, embed, done |
| Shipping unminified `bulma.css` in the binary | Always use `bulma.min.css` |
| Inline `<style>` blocks duplicating Bulma utilities | Use Bulma helper classes: `.has-text-centered`, `.mt-4`, `.is-flex`, etc. |
| Mixing Bootstrap and Bulma classes in the same page | Pick one. For Bootstrap, defer to the `html-first-frontend` agent |
| Forgetting `defer` on `<script src="/static/app.js">` | `defer` is required — the helpers query the DOM at load time |
| Reaching for jQuery or Alpine for navbar/modal | The scaffolded `app.js` already covers Bulma's four interactive patterns |

## Reference Layout

A completed setup looks like:

```
.
├── go.mod
├── main.go
├── BULMA_VERSION
└── internal/
    └── ui/
        ├── embed.go
        ├── render.go
        ├── static/
        │   ├── bulma.min.css
        │   ├── app.js
        │   └── theme.css          (optional)
        └── templates/
            ├── layout.html
            └── index.html
```

## Error Handling

| Condition | Action |
|---|---|
| No `go.mod` | Abort: "Not a Go module — run `go mod init` first." |
| Go version `< 1.16` | Abort: "`//go:embed` requires Go 1.16+. Update the `go` directive in go.mod." |
| `curl` returns non-200 for `bulma.min.css` | Verify the version exists on npm: `https://www.npmjs.com/package/bulma`. The release tag and the npm version should match. |
| User insists on CDN at runtime | Push back: the skill's reason for existing is offline, zero-dep, fully standalone binaries. If they still want CDN, decline and let them wire it manually without this skill. |
| Bulma assets already present in the repo | Skip download; verify checksums against `BULMA_VERSION` and proceed to wire the handler, write `app.js` if missing, and add the template. |


---

**Source**: https://github.com/jgthms/bulma
**Author**: sgaunet
**Discovered via**: skillsdirectory.com
**Genre**: development

Related skills 6

caveman

★ Featured

Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.

juliusbrussee 167k
Development

secure-linux-web-hosting

★ Featured

Use when setting up, hardening, or reviewing a cloud server for self-hosting, including DNS, SSH, firewalls, Nginx, static-site hosting, reverse-proxying an app, HTTPS with Let's Encrypt or ACME clients, safe HTTP-to-HTTPS redirects, or optional post-launch network tuning such as BBR.

xixu-me 155k
Development

readme-i18n

★ Featured

Use when the user wants to translate a repository README, make a repo multilingual, localize docs, add a language switcher, internationalize the README, or update localized README variants in a GitHub-style repository.

xixu-me 155k
Development

lark-shared

★ Featured

Use when first setting up lark-cli, running auth login, switching user/bot identity (--as), handling permission denied or scope errors, needing to update lark-cli, or seeing _notice in JSON output.

larksuite 155k
Development

improve-codebase-architecture

★ Featured

Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable.

mattpocock 151k
Development

paper-context-resolver

★ Featured

Optional RigorPilot helper for README-first deep learning repo reproduction. Use only when the README and repository files leave a narrow reproduction-critical gap and the task is to resolve a specific paper detail such as dataset split, preprocessing, evaluation protocol, checkpoint mapping, or runtime assumption from primary paper sources while recording conflicts. Do not use for general paper summary, repo scanning, environment setup, command execution, title-only paper lookup, or replacin...

lllllllama 127k
Development