Typeset
Theme-aware reading rhythm for semantic HTML and rendered rich content.
- Source
app/components/nitro_kit/typeset.rb- API
NitroKit::Typeset.new { plain HTML or rendered rich content }
Rendered content
One wrapper gives ordinary semantic HTML a coherent reading rhythm.
Article
A system for durable interfaces
Typed components give people and coding agents the same small, dependable vocabulary.
What the wrapper owns
- Reading rhythm and heading scale
- Lists, links, code, quotes, and tables
- Theme-aware color and typography tokens
The surrounding Container still owns the readable measure.
test/dummy/app/components/gallery/components/typeset_page.rbrender NitroKit::Container.new(size: :md) do
render NitroKit::Typeset.new(id: "gallery-typeset-article") do
h1 { "A system for durable interfaces" }
p do
"Typed components give people and coding agents the same " \
"small, dependable vocabulary."
end
h2 { "What the wrapper owns" }
ul do
li { "Reading rhythm and heading scale" }
li { "Lists, links, code, quotes, and tables" }
li { "Theme-aware color and typography tokens" }
end
blockquote do
"The surrounding Container still owns the readable measure."
end
end
endLong-form prose
A complete document exercises every element the wrapper styles: heading levels, paragraphs, links, lists, a quotation, inline and block code, a table, and a rule.
Release note
Nitro Kit 2.0 is agent-native
Nitro Kit 2.0 replaces generated component copies with a gem-owned library of Phlex components. A person and a coding agent now read the same contract, so the component contract table is the whole API surface there is to learn.
What changed
Every component takes explicit keywords, validates its own vocabulary, and raises on anything it does not recognize. Nothing is silently ignored.
- Options are closed sets. Passing
variant: :fancyraises an ArgumentError instead of rendering an unstyled control. - Styling hangs off data attributes, so no class strings travel through your templates.
- Themes are ordinary custom properties under the
--nk-prefix.
Rendering a component
Direct Phlex composition is the only public API:
render NitroKit::PageHeader.new(title: "Workspace members") do |header|
header.actions NitroKit::ButtonGroup.new(label: "Member actions") do |actions|
actions.button("Invite teammate", variant: :primary)
end
end
Upgrading
- Remove the generated component directory from your application.
- Add the gem and run
bin/rails nitro_kit:install. - Replace each helper call with the component it wrapped.
The fastest way to make an interface legible to an agent is to make it legible to a person first.
Support window
| Release | Status | Security fixes until |
|---|---|---|
| 2.0 | Current | Ongoing |
| 1.2 | Maintenance | July 2027 |
| 1.1 | Ended | January 2026 |
Questions belong in the discussion board; regressions belong in an issue with a reproduction.
test/dummy/app/components/gallery/components/typeset_page.rbrender NitroKit::Container.new(size: :md) do
render NitroKit::Typeset.new(id: "gallery-typeset-release-note") do
h1 { "Nitro Kit 2.0 is agent-native" }
p do
plain "Nitro Kit 2.0 replaces generated component copies with a "
strong { "gem-owned" }
plain " library of Phlex components. A person and a coding agent now read the same contract, so the "
a(href: "#contract") { "component contract table" }
plain " is the whole API surface there is to learn."
end
h2 { "What changed" }
p do
"Every component takes explicit keywords, validates its own vocabulary, and raises on anything it does not recognize. Nothing is silently ignored."
end
ul do
li do
plain "Options are closed sets. Passing "
code { "variant: :fancy" }
plain " raises an ArgumentError instead of rendering an unstyled control."
end
li { "Styling hangs off data attributes, so no class strings travel through your templates." }
li do
plain "Themes are ordinary custom properties under the "
code { "--nk-" }
plain " prefix."
end
end
h3 { "Rendering a component" }
p { "Direct Phlex composition is the only public API:" }
pre do
code do
plain <<~RUBY
render NitroKit::PageHeader.new(title: "Workspace members") do |header|
header.actions NitroKit::ButtonGroup.new(label: "Member actions") do |actions|
actions.button("Invite teammate", variant: :primary)
end
end
RUBY
end
end
h2 { "Upgrading" }
ol do
li { "Remove the generated component directory from your application." }
li do
plain "Add the gem and run "
code { "bin/rails nitro_kit:install" }
plain "."
end
li { "Replace each helper call with the component it wrapped." }
end
blockquote do
p do
"The fastest way to make an interface legible to an agent is to make it legible to a person first."
end
end
hr
h2 { "Support window" }
table do
thead do
tr do
th { "Release" }
th { "Status" }
th { "Security fixes until" }
end
end
tbody do
tr do
td { "2.0" }
td { "Current" }
td { "Ongoing" }
end
tr do
td { "1.2" }
td { "Maintenance" }
td { "July 2027" }
end
tr do
td { "1.1" }
td { "Ended" }
td { "January 2026" }
end
end
end
p do
plain "Questions belong in the "
a(href: "#discussions") { "discussion board" }
plain "; regressions belong in an issue with a reproduction."
end
end
endApplication boundaries
Nested Nitro components and explicit data-typeset=off regions keep their own styling.
Embedded component
Rich content can introduce an application-owned action.
A formatted heading inside the wrapper
The same heading level inside data-typeset=off loses its scale
test/dummy/app/components/gallery/components/typeset_page.rbrender NitroKit::Typeset.new(id: "gallery-typeset-boundary") do
p { "Rich content can introduce an application-owned action." }
render NitroKit::Card.new(id: "gallery-typeset-card") do |card|
card.title("Continue in the application")
card.body { render NitroKit::Button.new("Open workspace") }
end
h3 { "A formatted heading inside the wrapper" }
div(data: { typeset: "off" }, id: "gallery-typeset-opt-out") do
h3 { "The same heading level inside data-typeset=off loses its scale" }
end
endComponent contract
Constructor options, rendered root, closed vocabularies, and compound boundary for this component, exactly as shipped.
docs/component_contracts.md · NitroKit::Typeset
- Constructor-specific options
id: nil
- Root and closed vocabulary
div[data-nk=typeset]- Compound contract
- Requires direct content. Styles semantic rich content without constraining its width. Nested Nitro component roots and
data-typeset="off"regions establish styling boundaries. The shipped stylesheet retains the@scopepath and includes a low-specificity fallback for engines that do not parse@scope, including Firefox through 145. That fallback covers root typography plus explicitly anchored direct-child headings, flow elements, lists, code/pre, tables, and links within those supported elements; it is a documented semantic subset rather than full descendant parity.
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.