Card
A compound surface with typed title, body, divider, and footer slots.
- Source
app/components/nitro_kit/card.rb- API
NitroKit::Card.new { |card| card.title; card.body; card.footer }
Anatomy
The card owns structural slots while headings and application content remain ordinary Phlex.
Complete structure
test/dummy/app/components/gallery/components/card_page.rbsample("Title, body, divider, footer", slug: "complete") do
render NitroKit::Card.new(id: "gallery-card-workspace") do |card|
card.title("Mothership workspace", level: 3)
card.body { "12 active members · Team plan" }
card.divider
card.footer { "Renews August 1, 2026" }
end
end
sample("Body only", slug: "body-only") do
render NitroKit::Card.new(id: "gallery-card-body-only") do |card|
card.body { "A quiet surface can omit title and footer slots." }
end
endContent modes
Cards support full-width regions and long product content without assuming its internal markup.
Full-width region
Weekly activity
test/dummy/app/components/gallery/components/card_page.rbrender NitroKit::Card.new(id: "gallery-card-activity") do |card|
card.title("Weekly activity", level: 3)
card.full do
card.body("Chart placeholder · 1,284 events")
end
card.footer("Updated July 13, 2026 at 08:42 UTC")
endLong content
A workspace name that remains understandable when translated or supplied by a customer
This description deliberately spans multiple lines so the surface demonstrates natural wrapping without a special long-content mode or application utility classes.
test/dummy/app/components/gallery/components/card_page.rbrender NitroKit::Card.new(id: "gallery-card-long-content") do |card|
card.title(
"A workspace name that remains understandable when translated or supplied by a customer",
level: 3
)
card.body do
p do
"This description deliberately spans multiple lines so the surface demonstrates natural wrapping " \
"without a special long-content mode or application utility classes."
end
end
card.footer("Last reviewed by Ada Lovelace")
endHeading levels
The title slot emits the requested semantic heading level from one through six.
Complete title scale
Level 1 title
Level 2 title
Level 3 title
Level 4 title
Level 5 title
Level 6 title
test/dummy/app/components/gallery/components/card_page.rb(1..6).each do |level|
sample("Heading level #{level}", slug: "heading-#{level}") do
render NitroKit::Card.new(id: "gallery-card-heading-#{level}") do |card|
card.title("Level #{level} title", level:)
card.body { "Card titles follow the surrounding document hierarchy." }
end
end
endSlot boundaries
Individual slots and useful partial structures remain valid without placeholder content.
Partial structures
A title without supporting content
test/dummy/app/components/gallery/components/card_page.rbsample("Empty surface", slug: "empty") do
render NitroKit::Card.new(id: "gallery-card-empty") { nil }
end
sample("Title only", slug: "title-only") do
render NitroKit::Card.new(id: "gallery-card-title-only") do |card|
card.title("A title without supporting content", level: 3)
end
end
sample("Footer only", slug: "footer-only") do
render NitroKit::Card.new(id: "gallery-card-footer-only") do |card|
card.footer("Last synchronized July 13, 2026")
end
end
sample("Body and footer", slug: "body-footer") do
render NitroKit::Card.new(id: "gallery-card-body-footer") do |card|
card.body("Three pending invitations")
card.footer("Review access before the next billing cycle")
end
end
sample("Full-width only", slug: "full-only") do
render NitroKit::Card.new(id: "gallery-card-full-only") do |card|
card.full { "A full-width region can be the only declared slot." }
end
endRecord composition
Status, structured record metadata, and related actions nest without changing card anatomy.
Integration detail
Slack
Post alerts to the team operations channel.
| Connected | |
|---|---|
| Delivery target | Team operations channel |
test/dummy/app/components/gallery/components/card_page.rbintegration = Gallery::Data.integrations.fetch(1)
render NitroKit::Card.new(id: "gallery-card-integration") do |card|
card.title(integration.name, level: 3)
card.body do
render NitroKit::Badge.new(
"Action required",
id: "gallery-card-integration-status",
color: :warning,
size: :sm
)
p { integration.description }
render NitroKit::DetailsTable.new(
integration,
id: "gallery-card-integration-details",
label: "Slack integration details"
) do |details|
details.field(:connected_at, label: "Connected")
details.field(:delivery_target, label: "Delivery target", value: "Team operations channel")
end
end
card.divider
card.footer do
render NitroKit::ButtonGroup.new(
id: "gallery-card-integration-actions",
label: "Slack integration actions"
) do |group|
group.button(
"Reconnect",
id: "gallery-card-integration-reconnect",
variant: :primary,
size: :sm,
icon: :refresh_cw
)
group.button(
"Disconnect",
id: "gallery-card-integration-disconnect",
variant: :destructive,
size: :sm
)
end
end
endForm composition
Card, Field, Input, and Button compose directly into a native profile form.
Profile settings
Profile
test/dummy/app/components/gallery/components/card_page.rbrender NitroKit::Card.new(id: "gallery-card-profile-form-card") do |card|
card.title("Profile", level: 3)
card.body do
form(id: "gallery-card-profile-form", action: "#profile", method: "post") do
render NitroKit::FieldGroup.new do
render NitroKit::Field.new(
nil,
:name,
id: "gallery-card-profile-name",
name: "profile[name]",
value: "Ada Lovelace",
label: "Name",
autocomplete: "name",
required: true,
html: { id: "gallery-card-profile-name-field" }
)
render NitroKit::Field.new(
nil,
:email,
as: :email,
id: "gallery-card-profile-email",
name: "profile[email]",
value: "[email protected]",
label: "Email",
description: "Used for security notices and account recovery.",
autocomplete: "email",
required: true,
html: { id: "gallery-card-profile-email-field" }
)
end
end
end
card.footer do
render NitroKit::Button.new(
"Save profile",
id: "gallery-card-profile-save",
type: :submit,
form: "gallery-card-profile-form",
variant: :primary,
icon: :save
)
render NitroKit::Button.new(
"Reset",
id: "gallery-card-profile-reset",
type: :reset,
form: "gallery-card-profile-form",
)
end
endComponent contract
Constructor options, rendered root, closed vocabularies, and compound boundary for this component, exactly as shipped.
docs/component_contracts.md · NitroKit::Card
- Constructor-specific options
id: nil
- Root and closed vocabulary
article[data-nk=card]; title levels1..6- Compound contract
- Requires a content block.
title,body,footer,divider, andfullrender immediately; their count and order are not constrained.title,body, andfooterrequire non-blank text or a block, andfullrequires a block. The shadowed Phlex elements remain available ashtml_title,html_body, andhtml_footer.
Relevant patterns
Application conventions this component belongs to. Each summary is the leading section of its pattern document.
docs/patterns/inline_edit.md
Inline edit
- Wrap the complete resource region in one stable Turbo Frame.
- Show, edit, validation failure, success, and Cancel return the same frame ID.
- Invalid updates render with
422; successful updates redirect with303. - Use a Turbo Stream only when the update changes another region too. if editing render UI::ProjectForm.new(project) else div do h2 { project.name } render NitroKit::Button.new("Edit", href: edit_project_path(project)) end end
System rules for coding agents
Instructions for coding agents, repeated on every component page so one fetched page has enough context. Humans can usually skip this section.
test/dummy/app/components/gallery/agent_rules.rb
View agent instructions
- Nitro Kit 2.0 is a gem-owned, Phlex-only UI system for Rails. Render components directly:
render NitroKit::Button.new("Save", variant: :primary). - Compose Nitro components inside application-owned Phlex classes. Public initializers and declared slots are the component API; private methods are not.
- Public options are explicit keywords, and enumerated options are closed vocabularies. Invalid names or values raise
ArgumentError; this component's accepted options are in the contract above. - Pass native attributes through their typed boundary:
html:for HTML,aria:for ARIA, anddata:for application data.classandstyleare forbidden, including insidehtml:. - Nitro owns
NitroKit::Component::RESERVED_DATA_ATTRIBUTES:data-nk,data-slot,data-variant,data-size,data-nk-escape,data-enhanced,data-state,data-disabled,data-required,data-orientation,data-presentation,data-placement,data-layout,data-side,data-field-type,data-dir,data-gap,data-align,data-justify,data-wrap,data-cols,data-mode,data-key. Do not pass them throughdata:.data-action,data-controllerare additive and compose with Nitro behavior. - If an integration truly requires a class, use
desperately_need_a_class:. It requires a non-blank String and marks the exception withdata-nk-escape="class". - Every root emits
data-nk; owned parts emit component-qualifieddata-slotvalues such asfield-controlorcard-title. Select on those attributes, never on classes. - Customize components with documented
--nk-*custom properties in an application stylesheet. Variables beginning with--_nk-*are private component mechanics. - Use
NitroKit::FlexandNitroKit::Gridfor layout. Parents own external placement and available width; components own their intrinsic geometry. - Preserve native elements and accessibility semantics. State is exposed through native semantics and ARIA first, and through
data-statewhen styling or behavior also needs it.