Skip to content

Lesson 2 — Wiring Alert dismissal

Alert already exists in Phlex::UI from Module 5 but its dismiss button was a placeholder — it rendered the × but clicking it did nothing. Now we wire it properly.

This is the simplest possible Stimulus integration — one controller, one action, no targets, no values — and it demonstrates the core pattern cleanly before we build more complex components.

The component

Update Components::Alert to add data-controller and wire the dismiss button:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# app/components/alert.rb
class Components::Alert < Components::Base
  # ... existing props and VARIANTS unchanged ...

  def view_template
    div(class: alert_classes, role: "alert", data: { controller: "alert" }) do
      div(class: "flex items-start gap-3") do
        Icon(name: styles[:icon], class_name: "h-5 w-5 shrink-0")
        span(class: "flex-1 text-sm") { @message }
        if @dismissible
          button(
            type:  "button",
            class: "ml-auto shrink-0 opacity-70 hover:opacity-100 " \
              "text-current focus:outline-none",
            data:  { action: "click->alert#dismiss" }
          ) do
            Icon(name: :x_mark, class_name: "h-4 w-4")
          end
        end
      end
    end
  end
end

data-controller: "alert" tells Stimulus this element is managed by the alert controller. data-action: "click->alert#dismiss" tells Stimulus to call dismiss() on the alert controller when this button is clicked.

As a quick test, add the following as the first line in the view_template for the Views::Boards::Show view.

1
2
3
  def view_template
    Alert(message: "A test alert", variant: :danger, dismissible: true)
    ...

When you open go to the show page you should now be able to dismiss this alert. You can remove it when you’re convinced it’s working OK.

The Stimulus controller

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// app/javascript/controllers/alert_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  dismiss() {
    this.element.style.transition = "opacity 150ms ease-out"
    this.element.style.opacity    = "0"
    setTimeout(() => this.element.remove(), 150)
  }
}

this.element is always the element that has data-controller="alert" on it — the root div of the alert. The controller fades it out then removes it from the DOM entirely.

This is the complete pattern: Phlex generates the data-* attributes, Stimulus attaches the behaviour. The component owns the wiring; the controller owns the behaviour.