Alert
Displays a callout for user attention.
Usage
Example
An icon placed first gets its own column next to the title and description.
Alert do
info_icon
AlertTitle { "Heads up!" }
AlertDescription { "You can add components to your app using the CLI." }
end
Without icon
Alert do
AlertTitle { "Pro tip" }
AlertDescription { "Simply, don't include an icon and your alert will look like this." }
end
Destructive
Alert(variant: :destructive) do
circle_alert_icon
AlertTitle { "Something went wrong" }
AlertDescription { "Your session has expired. Please log in again." }
end
Custom colors
Alert ships the default and destructive variants. For any other color, pass class: with a light and a dark shade of the same scale, so the text stays readable in both themes.
Alert(class: "border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-50 *:data-[slot=alert-description]:text-amber-900/90 dark:*:data-[slot=alert-description]:text-amber-50/90") do
triangle_alert_icon
AlertTitle { "Your subscription will expire in 3 days." }
AlertDescription { "Renew now to avoid service interruption or upgrade to a paid plan to continue using the service." }
end
With action
AlertAction pins a control to the top-right corner.
Alert do
info_icon
AlertTitle { "Dark mode is now available" }
AlertDescription { "Enable it under your profile settings." }
AlertAction do
Button(variant: :outline, size: :sm) { "Enable" }
end
end
Static callout
Alert has role="alert": screen readers announce it right away, interrupting the user. Pass role: "status" to announce it without interrupting, or role: nil for a message that is part of the page.
Alert(role: nil) do
AlertTitle { "Pro tip" }
AlertDescription { "With RubyUI you'll ship faster." }
end
Title as a heading
AlertTitle is a plain div. To list the alert in the page's headings, put a heading of the right level inside it.
Billing
Alert(role: nil) do
AlertTitle { h3 { "Billing" } }
AlertDescription { "Your plan renews on the first day of each month." }
end
Installation
Using RubyUI CLI
Run the install command
rails g ruby_ui:component Alert
Manual installation
1
Add RubyUI::Alert to app/components/ruby_ui/alert/alert.rb
# frozen_string_literal: true
module RubyUI
# A callout for user attention. An icon placed as the first child sits in
# its own grid column next to the title and description, like shadcn's Alert.
# role="alert" makes screen readers announce it and interrupt the user. Pass
# role: "status" to announce it politely, or role: nil for a static callout.
# Variants mirror shadcn; pass class: for any other color.
class Alert < Base
VARIANTS = {
default: "bg-card text-card-foreground",
destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current"
}.freeze
def initialize(variant: :default, **attrs)
@variant = variant.to_s.to_sym
@variant = :default unless VARIANTS.key?(@variant) # nil or unknown: default look
super(**attrs) # must be called after variant is set
# mix joins string attributes ("alert status") and ignores nil, so the
# user's role replaces the default here, and nil removes it.
if attrs.key?(:role)
attrs[:role].nil? ? @attrs.delete(:role) : @attrs[:role] = attrs[:role]
end
end
def view_template(&)
div(**attrs, &)
end
private
def default_attrs
{
data: {slot: "alert"},
role: "alert",
class: [
"group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm",
"has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18",
"has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5",
"*:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
VARIANTS.fetch(@variant)
]
}
end
end
end
2
Add RubyUI::AlertAction to app/components/ruby_ui/alert/alert_action.rb
# frozen_string_literal: true
module RubyUI
# Top-right slot for a control (a button, a link). The Alert reserves right
# padding for it when present.
class AlertAction < Base
def view_template(&)
div(**attrs, &)
end
private
def default_attrs
{
data: {slot: "alert-action"},
class: "absolute top-2.5 right-3"
}
end
end
end
3
Add RubyUI::AlertDescription to app/components/ruby_ui/alert/alert_description.rb
# frozen_string_literal: true
module RubyUI
class AlertDescription < Base
def view_template(&)
div(**attrs, &)
end
private
def default_attrs
{
data: {slot: "alert-description"},
class: "text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground"
}
end
end
end
4
Add RubyUI::AlertDocs to app/components/ruby_ui/alert/alert_docs.rb
# frozen_string_literal: true
class Views::Docs::Alert < Views::Base
def view_template
component = "Alert"
div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
render Docs::Header.new(title: "Alert", description: "Displays a callout for user attention.")
Heading(level: 2) { "Usage" }
render Docs::VisualCodeExample.new(title: "Example", description: "An icon placed first gets its own column next to the title and description.", context: self) do
<<~RUBY
Alert do
info_icon
AlertTitle { "Heads up!" }
AlertDescription { "You can add components to your app using the CLI." }
end
RUBY
end
render Docs::VisualCodeExample.new(title: "Without Icon", context: self) do
<<~RUBY
Alert do
AlertTitle { "Pro tip" }
AlertDescription { "Simply, don't include an icon and your alert will look like this." }
end
RUBY
end
render Docs::VisualCodeExample.new(title: "Destructive", context: self) do
<<~RUBY
Alert(variant: :destructive) do
circle_alert_icon
AlertTitle { "Something went wrong" }
AlertDescription { "Your session has expired. Please log in again." }
end
RUBY
end
render Docs::VisualCodeExample.new(title: "Custom Colors", description: "Alert ships the default and destructive variants. For any other color, pass class: with a light and a dark shade of the same scale, so the text stays readable in both themes.", context: self) do
<<~RUBY
Alert(class: "border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-50 *:data-[slot=alert-description]:text-amber-900/90 dark:*:data-[slot=alert-description]:text-amber-50/90") do
triangle_alert_icon
AlertTitle { "Your subscription will expire in 3 days." }
AlertDescription { "Renew now to avoid service interruption or upgrade to a paid plan to continue using the service." }
end
RUBY
end
render Docs::VisualCodeExample.new(title: "With Action", description: "AlertAction pins a control to the top-right corner.", context: self) do
<<~RUBY
Alert do
info_icon
AlertTitle { "Dark mode is now available" }
AlertDescription { "Enable it under your profile settings." }
AlertAction do
Button(variant: :outline, size: :sm) { "Enable" }
end
end
RUBY
end
render Docs::VisualCodeExample.new(title: "Static callout", description: "Alert has role=\"alert\": screen readers announce it right away, interrupting the user. Pass role: \"status\" to announce it without interrupting, or role: nil for a message that is part of the page.", context: self) do
<<~RUBY
Alert(role: nil) do
AlertTitle { "Pro tip" }
AlertDescription { "With RubyUI you'll ship faster." }
end
RUBY
end
render Docs::VisualCodeExample.new(title: "Title as a heading", description: "AlertTitle is a plain div. To list the alert in the page's headings, put a heading of the right level inside it.", context: self) do
<<~RUBY
Alert(role: nil) do
AlertTitle { h3 { "Billing" } }
AlertDescription { "Your plan renews on the first day of each month." }
end
RUBY
end
render Components::ComponentSetup::Tabs.new(component_name: component)
render Docs::ComponentsTable.new(component_files(component))
end
end
private
# lucide icons, as lucide-react renders them (24x24, aria-hidden, no size class):
# the Alert sizes them to 16px.
def lucide_icon(&)
svg(
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewbox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
stroke_width: "2",
stroke_linecap: "round",
stroke_linejoin: "round",
aria_hidden: "true",
&
)
end
def info_icon
lucide_icon do |s|
s.circle(cx: "12", cy: "12", r: "10")
s.path(d: "M12 16v-4")
s.path(d: "M12 8h.01")
end
end
def circle_alert_icon
lucide_icon do |s|
s.circle(cx: "12", cy: "12", r: "10")
s.line(x1: "12", x2: "12", y1: "8", y2: "12")
s.line(x1: "12", x2: "12.01", y1: "16", y2: "16")
end
end
def triangle_alert_icon
lucide_icon do |s|
s.path(d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3")
s.path(d: "M12 9v4")
s.path(d: "M12 17h.01")
end
end
def circle_check_icon
lucide_icon do |s|
s.circle(cx: "12", cy: "12", r: "10")
s.path(d: "m16 9-5.5 5.5L8 12")
end
end
end
5
Add RubyUI::AlertTitle to app/components/ruby_ui/alert/alert_title.rb
# frozen_string_literal: true
module RubyUI
class AlertTitle < Base
def view_template(&)
div(**attrs, &)
end
private
def default_attrs
{
data: {slot: "alert-title"},
class: "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground"
}
end
end
end
Components
| Component | Built using | Source |
|---|---|---|
Alert | Phlex | |
AlertAction | Phlex | |
AlertDescription | Phlex | |
AlertDocs | Phlex | |
AlertTitle | Phlex |