Components
Modals
Theme
Hound
Framework
Tailwind CSS
JavaScript
Use the bundled model component to add dialogs to your application. These could be leveraged for notifications, notices, or custom content.
All modals can be closed using an action-based element (button, close icon, etc...) or the esc key on your keyboard.
Modals require Stimulus.js and stimulus-use for transition effects. The modal_controller.js controller is integrated to the Hound theme by default.
Modals
Base
Modal Title
Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.
Demo
Modal Title
Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.
<div data-controller="modal">
<button type="button" data-action="click->modal#open" class="btn btn-primary" tabindex="0">
Open modal
</button>
<div
aria-labelledby="modal-title"
aria-modal="true"
data-action="keyup@window->modal#closeWithEsc"
data-modal-target="container"
class="hidden fixed inset-0 z-50 overflow-y-auto"
role="dialog"
>
<div class="h-screen w-full relative flex items-center justify-center">
<div
data-modal-target="content"
data-transition-enter-active="transition ease-out duration-300"
data-transition-enter-from="transform opacity-0 scale-95"
data-transition-enter-to="transform opacity-100 scale-100"
data-transition-leave-active="transition ease-in duration-300"
data-transition-leave-from="transform opacity-100 scale-100"
data-transition-leave-to="transform opacity-0 scale-95"
class="hidden rounded shadow-xl max-w-lg bg-white m-1 p-8 prose prose-indigo origin-bottom mx-auto dark:bg-slate-700 dark:text-slate-200">
<h2 id="modal-title" class="dark:text-slate-100">Modal Title</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.</p>
<div class="flex justify-end items-center flex-wrap space-x-4">
<button class="btn btn-primary" data-action="click->modal#close">Close</button>
</div>
</div>
</div>
</div>
</div>
%div{"data-controller" => "modal"}
%button.btn.btn-primary{"data-action" => "click->modal#open", tabindex: "0", type: "button"}
Open modal
.hidden.fixed.inset-0.z-50.overflow-y-auto{"aria-labelledby" => "modal-title", "aria-modal" => "true", "data-action" => "keyup@window->modal#closeWithEsc", "data-modal-target" => "container", role: "dialog"}
.h-screen.w-full.relative.flex.items-center.justify-center
.hidden.rounded.shadow-xl.max-w-lg.bg-white.m-1.p-8.prose.prose-indigo.origin-bottom.mx-auto.dark:bg-slate-700.dark:text-slate-200{"data-modal-target" => "content", "data-transition-enter-active" => "transition ease-out duration-300", "data-transition-enter-from" => "transform opacity-0 scale-95", "data-transition-enter-to" => "transform opacity-100 scale-100", "data-transition-leave-active" => "transition ease-in duration-300", "data-transition-leave-from" => "transform opacity-100 scale-100", "data-transition-leave-to" => "transform opacity-0 scale-95"}
%h2#modal-title.dark:text-slate-100 Modal Title
%p Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.
.flex.justify-end.items-center.flex-wrap.space-x-4
%button.btn.btn-primary{"data-action" => "click->modal#close"} Close
// app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { useTransition, useClickOutside } from 'stimulus-use'
export default class extends Controller {
static targets = ['container', 'content']
connect() {
useTransition(this, {
element: this.contentTarget
})
useClickOutside(this, {
element: this.contentTarget
})
}
open(event) {
event.preventDefault()
this.enableAppearance()
this.toggleTransition()
}
close(event) {
event.preventDefault()
this.leave()
this.disableAppearance()
}
clickOutside(event) {
const action = event.target.dataset.action
if (action == "click->modal#open" || action == "click->modal#open:prevent") {
return
}
this.close(event)
}
closeWithEsc(event){
if (event.keyCode === 27 && !this.containerTarget.classList.contains('hidden')) {
this.close(event)
}
}
enableAppearance() {
this.containerTarget.classList.add("bg-black/80")
this.containerTarget.classList.remove('hidden')
}
disableAppearance() {
this.containerTarget.classList.add('hidden')
this.containerTarget.classList.remove("bg-black/80")
}
disconnect() {
this.toggleTransition()
}
}
Modals
Single action
Time to launch
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Demo
Time to launch
Lorem ipsum dolor sit amet consectetur adipisicing elit.
<div data-controller="modal">
<button type="button" data-action="click->modal#open" class="btn btn-primary" tabindex="0">
Open single action modal
</button>
<div
aria-labelledby="single-action-modal-title"
aria-modal="true"
data-modal-target="container"
class="hidden fixed inset-0 z-50 overflow-y-auto"
role="dialog"
>
<div class="h-screen w-full relative flex items-center justify-center">
<div
data-modal-target="content"
data-action="keyup@window->modal#closeWithEsc"
data-transition-enter-active="transition ease-out duration-300"
data-transition-enter-from="transform opacity-0 scale-95"
data-transition-enter-to="transform opacity-100 scale-100"
data-transition-leave-active="transition ease-in duration-300"
data-transition-leave-from="transform opacity-100 scale-100"
data-transition-leave-to="transform opacity-0 scale-95"
class="hidden rounded mx-auto shadow-xl max-w-xs bg-white m-1 p-8 origin-bottom text-center dark:bg-slate-700 dark:text-slate-200 text-slate-600">
<div class="flex justify-center">
<div class="w-12 h-12 rounded-full bg-emerald-50 flex items-center justify-center mb-3">
<%= icon "rocket-launch", classes: "w-6 h-6 text-emerald-600 flex-shrink-0" %>
</div>
</div>
<h3 id="single-action-modal-title" class="font-semibold text-xl text-slate-800 dark:text-slate-100">Time to launch</h3>
<p class="text-sm mt-2 mb-4">Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
<button type="button" class="btn btn-primary w-full" data-action="click->modal#close">Launch away</button>
</div>
</div>
</div>
</div>
%div{"data-controller" => "modal"}
%button.btn.btn-primary{"data-action" => "click->modal#open", tabindex: "0", type: "button"}
Open single action modal
.hidden.fixed.inset-0.z-50.overflow-y-auto{"aria-labelledby" => "single-action-modal-title", "aria-modal" => "true", "data-modal-target" => "container", role: "dialog"}
.h-screen.w-full.relative.flex.items-center.justify-center
.hidden.rounded.mx-auto.shadow-xl.max-w-xs.bg-white.m-1.p-8.origin-bottom.text-center.dark:bg-slate-700.dark:text-slate-200.text-slate-600{"data-action" => "keyup@window->modal#closeWithEsc", "data-modal-target" => "content", "data-transition-enter-active" => "transition ease-out duration-300", "data-transition-enter-from" => "transform opacity-0 scale-95", "data-transition-enter-to" => "transform opacity-100 scale-100", "data-transition-leave-active" => "transition ease-in duration-300", "data-transition-leave-from" => "transform opacity-100 scale-100", "data-transition-leave-to" => "transform opacity-0 scale-95"}
.flex.justify-center
.w-12.h-12.rounded-full.bg-emerald-50.flex.items-center.justify-center.mb-3
= icon "rocket-launch", classes: "w-6 h-6 text-emerald-600 flex-shrink-0"
%h3#single-action-modal-title.font-semibold.text-xl.text-slate-800.dark:text-slate-100 Time to launch
%p.text-sm.mt-2.mb-4 Lorem ipsum dolor sit amet consectetur adipisicing elit.
%button.btn.btn-primary.w-full{"data-action" => "click->modal#close", type: "button"} Launch away
// app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { useTransition, useClickOutside } from 'stimulus-use'
export default class extends Controller {
static targets = ['container', 'content']
connect() {
useTransition(this, {
element: this.contentTarget
})
useClickOutside(this, {
element: this.contentTarget
})
}
open(event) {
event.preventDefault()
this.enableAppearance()
this.toggleTransition()
}
close(event) {
event.preventDefault()
this.leave()
this.disableAppearance()
}
clickOutside(event) {
const action = event.target.dataset.action
if (action == "click->modal#open" || action == "click->modal#open:prevent") {
return
}
this.close(event)
}
closeWithEsc(event){
if (event.keyCode === 27 && !this.containerTarget.classList.contains('hidden')) {
this.close(event)
}
}
enableAppearance() {
this.containerTarget.classList.add("bg-black/80")
this.containerTarget.classList.remove('hidden')
}
disableAppearance() {
this.containerTarget.classList.add('hidden')
this.containerTarget.classList.remove("bg-black/80")
}
disconnect() {
this.toggleTransition()
}
}
Modals
Centered dual action
Time to launch
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illum accusamus laudantium quis asperiores, suscipit!
Demo
Time to launch
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illum accusamus laudantium quis asperiores, suscipit!
<div data-controller="modal">
<button type="button" data-action="click->modal#open:prevent" class="btn btn-primary" tabindex="0">Open centered dual action modal</button>
<div
aria-labelledby="dual-action-modal-title"
aria-modal="true"
data-modal-target="container"
data-action="keyup@window->modal#closeWithEsc"
class="hidden fixed inset-0 z-50 overflow-y-auto"
role="dialog"
>
<div class="h-screen w-full relative flex items-center justify-center">
<div
data-modal-target="content"
data-transition-enter-active="transition ease-out duration-300"
data-transition-enter-from="transform opacity-0 scale-95"
data-transition-enter-to="transform opacity-100 scale-100"
data-transition-leave-active="transition ease-in duration-300"
data-transition-leave-from="transform opacity-100 scale-100"
data-transition-leave-to="transform opacity-0 scale-95"
class="hidden rounded mx-auto shadow-xl max-w-lg bg-white m-1 p-8 origin-bottom text-center dark:bg-slate-700 dark:text-slate-200">
<div class="flex justify-center">
<div class="w-12 h-12 rounded-full bg-emerald-50 flex items-center justify-center mb-3">
<%= icon "rocket-launch", classes: "w-6 h-6 text-emerald-600 flex-shrink-0" %>
</div>
</div>
<h3 id="dual-action-modal-title" class="font-semibold text-xl text-slate-800 dark:text-slate-100">Time to launch</h3>
<p class="mt-2 mb-4">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illum accusamus laudantium quis asperiores, suscipit!</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button class="btn btn-white w-full sm:order-1 order-2" data-action="click->modal#close">Cancel</button>
<button class="btn btn-primary w-full sm:order-2 order-1">Proceed to launch</button>
</div>
</div>
</div>
</div>
</div>
%div{"data-controller" => "modal"}
%button.btn.btn-primary{"data-action" => "click->modal#open:prevent", tabindex: "0", type: "button"} Open centered dual action modal
.hidden.fixed.inset-0.z-50.overflow-y-auto{"aria-labelledby" => "dual-action-modal-title", "aria-modal" => "true", "data-action" => "keyup@window->modal#closeWithEsc", "data-modal-target" => "container", role: "dialog"}
.h-screen.w-full.relative.flex.items-center.justify-center
.hidden.rounded.mx-auto.shadow-xl.max-w-lg.bg-white.m-1.p-8.origin-bottom.text-center.dark:bg-slate-700.dark:text-slate-200{"data-modal-target" => "content", "data-transition-enter-active" => "transition ease-out duration-300", "data-transition-enter-from" => "transform opacity-0 scale-95", "data-transition-enter-to" => "transform opacity-100 scale-100", "data-transition-leave-active" => "transition ease-in duration-300", "data-transition-leave-from" => "transform opacity-100 scale-100", "data-transition-leave-to" => "transform opacity-0 scale-95"}
.flex.justify-center
.w-12.h-12.rounded-full.bg-emerald-50.flex.items-center.justify-center.mb-3
= icon "rocket-launch", classes: "w-6 h-6 text-emerald-600 flex-shrink-0" %>
%h3#dual-action-modal-title.font-semibold.text-xl.text-slate-800.dark:text-slate-100 Time to launch
%p.mt-2.mb-4 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illum accusamus laudantium quis asperiores, suscipit!
.grid.grid-cols-1.sm:grid-cols-2.gap-3
%button.btn.btn-white.w-full.sm:order-1.order-2{"data-action" => "click->modal#close"} Cancel
%button.btn.btn-primary.w-full.sm:order-2.order-1 Proceed to launch
// app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { useTransition, useClickOutside } from 'stimulus-use'
export default class extends Controller {
static targets = ['container', 'content']
connect() {
useTransition(this, {
element: this.contentTarget
})
useClickOutside(this, {
element: this.contentTarget
})
}
open(event) {
event.preventDefault()
this.enableAppearance()
this.toggleTransition()
}
close(event) {
event.preventDefault()
this.leave()
this.disableAppearance()
}
clickOutside(event) {
const action = event.target.dataset.action
if (action == "click->modal#open" || action == "click->modal#open:prevent") {
return
}
this.close(event)
}
closeWithEsc(event){
if (event.keyCode === 27 && !this.containerTarget.classList.contains('hidden')) {
this.close(event)
}
}
enableAppearance() {
this.containerTarget.classList.add("bg-black/80")
this.containerTarget.classList.remove('hidden')
}
disableAppearance() {
this.containerTarget.classList.add('hidden')
this.containerTarget.classList.remove("bg-black/80")
}
disconnect() {
this.toggleTransition()
}
}
Modals
With dismissable icon
Delete account
Once your account is deleted it is gone for good. There is no data saved that we keep on hand. Are you sure you want to continue?
Demo
Delete account
Once your account is deleted it is gone for good. There is no data saved that we keep on hand. Are you sure you want to continue?
<div data-controller="modal">
<button type="button" data-action="click->modal#open" class="btn btn-primary" tabindex="0">
Open dismissable with icon modal
</button>
<div
aria-labelledby="dismissable-action-modal-title"
aria-modal="true"
data-modal-target="container"
class="hidden fixed inset-0 z-50 overflow-y-auto"
role="dialog">
<div class="h-screen w-full relative flex items-center justify-center">
<div
data-modal-target="content"
data-action="keyup@window->modal#closeWithEsc"
data-transition-enter-active="transition ease-out duration-300"
data-transition-enter-from="transform opacity-0 scale-95"
data-transition-enter-to="transform opacity-100 scale-100"
data-transition-leave-active="transition ease-in duration-300"
data-transition-leave-from="transform opacity-100 scale-100"
data-transition-leave-to="transform opacity-0 scale-95"
class="hidden rounded mx-auto shadow-xl sm:w-full sm:max-w-lg bg-white m-1 p-6 origin-bottom items-start relative dark:bg-slate-700 dark:text-slate-200">
<div class="flex items-start">
<button type="button" class="absolute top-2 right-2 w-8 h-8 bg-transparent hover:bg-slate-50 flex items-center justify-center rounded-full group dark:hover:bg-slate-50/50" data-action="click->modal#close">
<%= icon "x-mark", classes: "text-slate-400 w-6 h-6 group-hover:text-slate-500 pointer-events-none dark:group-hover:text-slate-800 dark:text-slate-400" %>
</button>
<div class="w-8 h-8 rounded-full bg-red-50 flex items-center justify-center mr-3 dark:bg-red-500/30">
<%= icon "exclamation-circle", classes: "w-6 h-6 text-red-600 flex-shrink-0 dark:text-red-300" %>
</div>
<div class="flex-1">
<h3 id="dismissable-action-modal-title" class="font-semibold text-lg text-slate-800 leading-tight mb-2 pt-1 dark:text-slate-100">Delete account</h3>
<p class="leading-snug text-sm mb-6 mt-1">Once your account is deleted it is gone for good. There is no data saved that we keep on hand. Are you sure you want to continue?</p>
<div class="flex justify-end items-center space-x-3">
<button class="btn btn-light" data-action="click->modal#close">Cancel</button>
<button class="btn bg-red-600 text-white focus:ring-red-100 hover:bg-red-700 hover:shadow-sm hover:shadow-slate-600/10">Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
%div{"data-controller" => "modal"}
%button.btn.btn-primary{"data-action" => "click->modal#open", tabindex: "0", type: "button"}
Open dismissable with icon modal
.hidden.fixed.inset-0.z-50.overflow-y-auto{"aria-labelledby" => "dismissable-action-modal-title", "aria-modal" => "true", "data-modal-target" => "container", role: "dialog"}
.h-screen.w-full.relative.flex.items-center.justify-center
.hidden.rounded.mx-auto.shadow-xl.sm:w-full.sm:max-w-lg.bg-white.m-1.p-6.origin-bottom.items-start.relative.dark:bg-slate-700.dark:text-slate-200{"data-action" => "keyup@window->modal#closeWithEsc", "data-modal-target" => "content", "data-transition-enter-active" => "transition ease-out duration-300", "data-transition-enter-from" => "transform opacity-0 scale-95", "data-transition-enter-to" => "transform opacity-100 scale-100", "data-transition-leave-active" => "transition ease-in duration-300", "data-transition-leave-from" => "transform opacity-100 scale-100", "data-transition-leave-to" => "transform opacity-0 scale-95"}
.flex.items-start
%button.absolute.top-2.right-2.w-8.h-8.bg-transparent.hover:bg-slate-50.flex.items-center.justify-center.rounded-full.group{class: "dark:hover:bg-slate-50/50", "data-action" => "click->modal#close", type: "button"}
= icon "x-mark", classes: "text-slate-400 w-6 h-6 group-hover:text-slate-500 pointer-events-none dark:group-hover:text-slate-800 dark:text-slate-400"
.w-8.h-8.rounded-full.bg-red-50.flex.items-center.justify-center.mr-3{class: "dark:bg-red-500/30"}
= icon "exclamation-circle", classes: "w-6 h-6 text-red-600 flex-shrink-0 dark:text-red-300"
.flex-1
%h3#dismissable-action-modal-title.font-semibold.text-lg.text-slate-800.leading-tight.mb-2.pt-1.dark:text-slate-100 Delete account
%p.leading-snug.text-sm.mb-6.mt-1 Once your account is deleted it is gone for good. There is no data saved that we keep on hand. Are you sure you want to continue?
.flex.justify-end.items-center.space-x-3
%button.btn.btn-light{"data-action" => "click->modal#close"} Cancel
%button.btn.bg-red-600.text-white.focus:ring-red-100.hover:bg-red-700.hover:shadow-sm{class: "hover:shadow-slate-600/10"} Delete
// app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { useTransition, useClickOutside } from 'stimulus-use'
export default class extends Controller {
static targets = ['container', 'content']
connect() {
useTransition(this, {
element: this.contentTarget
})
useClickOutside(this, {
element: this.contentTarget
})
}
open(event) {
event.preventDefault()
this.enableAppearance()
this.toggleTransition()
}
close(event) {
event.preventDefault()
this.leave()
this.disableAppearance()
}
clickOutside(event) {
const action = event.target.dataset.action
if (action == "click->modal#open" || action == "click->modal#open:prevent") {
return
}
this.close(event)
}
closeWithEsc(event){
if (event.keyCode === 27 && !this.containerTarget.classList.contains('hidden')) {
this.close(event)
}
}
enableAppearance() {
this.containerTarget.classList.add("bg-black/80")
this.containerTarget.classList.remove('hidden')
}
disableAppearance() {
this.containerTarget.classList.add('hidden')
this.containerTarget.classList.remove("bg-black/80")
}
disconnect() {
this.toggleTransition()
}
}
Modals
With form and action bar
Subscribe
Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.
Demo
Subscribe
Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.
<div data-controller="modal">
<button type="button" data-action="click->modal#open" class="btn btn-primary" tabindex="0">
Open modal with form
</button>
<div
aria-labelledby="modal-with-form"
aria-modal="true"
data-modal-target="container"
data-action="keyup@window->modal#closeWithEsc"
class="hidden fixed inset-0 z-50 overflow-y-auto"
role="dialog"
>
<div class="h-screen w-full relative flex items-center justify-center">
<div
data-modal-target="content"
data-transition-enter-active="transition ease-out duration-300"
data-transition-enter-from="transform opacity-0 scale-95"
data-transition-enter-to="transform opacity-100 scale-100"
data-transition-leave-active="transition ease-in duration-300"
data-transition-leave-from="transform opacity-100 scale-100"
data-transition-leave-to="transform opacity-0 scale-95"
class="hidden rounded shadow-xl mx-auto max-w-lg bg-white m-1 p-8 origin-bottom relative dark:bg-slate-700 dark:text-slate-200">
<button type="button" class="absolute top-2 right-2 w-8 h-8 bg-transparent hover:bg-slate-50 flex items-center justify-center rounded-full group dark:hover:bg-slate-50/50" data-action="click->modal#close">
<%= icon "x-mark", classes: "text-slate-400 w-6 h-6 group-hover:text-slate-500 pointer-events-none dark:group-hover:text-slate-800 dark:text-slate-400" %>
</button>
<div class="prose prose-indigo dark:!prose-invert mb-4">
<h2 id="modal-with-form" class="mb-3">Subscribe</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.</p>
</div>
<form action="#">
<div class="form-group">
<label for="email_demo" class="form-label">E-mail</label>
<input id="email_demo" type="email" class="form-input" placeholder="saasquatch@railsui.com">
</div>
<div class="form-group">
<label for="reason_demo" class="form-label">Reason for joining</label>
<textarea name="reason" id="reason_demo" class="form-input" placeholder="In a couple sentences let us know your reason for joining the Rails UI community"></textarea>
</div>
</form>
<div class="mt-8 flex justify-start items-center flex-wrap bg-gray-50 px-8 py-4 -mx-8 -mb-8 border-t border-slate-300 rounded-b shadow-inner space-x-4 dark:bg-slate-700 dark:text-slate-200 dark:border-slate-500/80">
<button class="btn btn-primary">Subscribe</button>
<button class="btn btn-white" data-action="click->modal#close">Cancel</button>
</div>
</div>
</div>
</div>
</div>
%div{"data-controller" => "modal"}
%button.btn.btn-primary{"data-action" => "click->modal#open", tabindex: "0", type: "button"}
Open modal with form
.hidden.fixed.inset-0.z-50.overflow-y-auto{"aria-labelledby" => "modal-with-form", "aria-modal" => "true", "data-action" => "keyup@window->modal#closeWithEsc", "data-modal-target" => "container", role: "dialog"}
.h-screen.w-full.relative.flex.items-center.justify-center
.hidden.rounded.shadow-xl.mx-auto.max-w-lg.bg-white.m-1.p-8.origin-bottom.relative.dark:bg-slate-700.dark:text-slate-200{"data-modal-target" => "content", "data-transition-enter-active" => "transition ease-out duration-300", "data-transition-enter-from" => "transform opacity-0 scale-95", "data-transition-enter-to" => "transform opacity-100 scale-100", "data-transition-leave-active" => "transition ease-in duration-300", "data-transition-leave-from" => "transform opacity-100 scale-100", "data-transition-leave-to" => "transform opacity-0 scale-95"}
%button.absolute.top-2.right-2.w-8.h-8.bg-transparent.hover:bg-slate-50.flex.items-center.justify-center.rounded-full.group{class: "dark:hover:bg-slate-50/50", "data-action" => "click->modal#close", type: "button"}
= icon "x-mark", classes: "text-slate-400 w-6 h-6 group-hover:text-slate-500 pointer-events-none dark:group-hover:text-slate-800 dark:text-slate-400"
.prose.prose-indigo.mb-4{class: "dark:!prose-invert"}
%h2#modal-with-form.mb-3 Subscribe
%p Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt, veritatis! Nulla, enim incidunt.
%form{action: "#"}
.form-group
%label.form-label{for: "email_demo"} E-mail
%input#email_demo.form-input{placeholder: "saasquatch@railsui.com", type: "email"}/
.form-group
%label.form-label{for: "reason_demo"} Reason for joining
%textarea#reason_demo.form-input{name: "reason", placeholder: "In a couple sentences let us know your reason for joining the Rails UI community"}
.mt-8.flex.justify-start.items-center.flex-wrap.bg-gray-50.px-8.py-4.-mx-8.-mb-8.border-t.border-slate-300.rounded-b.shadow-inner.space-x-4.dark:bg-slate-700.dark:text-slate-200{class: "dark:border-slate-500/80"}
%button.btn.btn-primary Subscribe
%button.btn.btn-white{"data-action" => "click->modal#close"} Cancel
// app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
import { useTransition, useClickOutside } from 'stimulus-use'
export default class extends Controller {
static targets = ['container', 'content']
connect() {
useTransition(this, {
element: this.contentTarget
})
useClickOutside(this, {
element: this.contentTarget
})
}
open(event) {
event.preventDefault()
this.enableAppearance()
this.toggleTransition()
}
close(event) {
event.preventDefault()
this.leave()
this.disableAppearance()
}
clickOutside(event) {
const action = event.target.dataset.action
if (action == "click->modal#open" || action == "click->modal#open:prevent") {
return
}
this.close(event)
}
closeWithEsc(event){
if (event.keyCode === 27 && !this.containerTarget.classList.contains('hidden')) {
this.close(event)
}
}
enableAppearance() {
this.containerTarget.classList.add("bg-black/80")
this.containerTarget.classList.remove('hidden')
}
disableAppearance() {
this.containerTarget.classList.add('hidden')
this.containerTarget.classList.remove("bg-black/80")
}
disconnect() {
this.toggleTransition()
}
}