| name | build-ui |
| description | Build UI components using the Crystal component system — knows all 94 HTML elements, 3 component tiers, composition patterns, and the builder DSL |
| user-invocable | true |
Build UI with the Crystal Component System
You are a front-end developer who builds server-rendered UIs using the asset pipeline's type-safe Crystal component system. All HTML is generated from Crystal classes — no templates needed.
Core Patterns
Creating Elements
# Every HTML tag is a Crystal class under Components::Elements
div = Components::Elements::Div.new(class: "container")
div << "Hello, World!"
div.render # => <div class="container">Hello, World!</div>
Builder DSL
article = Components::Elements::Article.new(class: "post").build do |art|
header = Components::Elements::Header.new
h1 = Components::Elements::H1.new
h1 << "Title"
header << h1
art << header
p = Components::Elements::P.new
p << "Content here."
art << p
end
article.render
Child Management
element << child — append child (String, HTMLElement, Component, or RawHTML)
element.add_children(child1, child2) — append multiple
element.build { |el| ... } — block DSL for building content
Components::Elements::RawHTML.new(html_string) — insert pre-rendered HTML without escaping
Element Reference (94 Classes)
All classes live under Components::Elements. Constructor: .new(**attrs) unless noted.
Document Elements
| Class | Tag | Factory Methods |
|---|
Html | <html> | — (render includes <!DOCTYPE html>) |
Head | <head> | — |
Body | <body> | — |
Title | <title> | — (text children only) |
Meta | <meta> | .charset(charset?), .viewport(content?), .description(text), .keywords(text), .author(text), .http_equiv(equiv, content) |
Link | <link> | .stylesheet(href), .icon(href, type?), .favicon(href), .apple_touch_icon(href, sizes?), .manifest(href), .preconnect(href), .prefetch(href), .preload(href, as_type) |
Script | <script> | .new(js_string) — content not escaped |
Style | <style> | .new(css_string) — content not escaped |
Section Elements
| Class | Tag | Notes |
|---|
Header | <header> | |
Footer | <footer> | |
Nav | <nav> | |
Main | <main> | |
Article | <article> | |
Section | <section> | |
Aside | <aside> | |
H1 - H6 | <h1> - <h6> | All inherit from Heading |
Grouping Elements
| Class | Tag | Notes |
|---|
Div | <div> | |
Span | <span> | |
P | <p> | |
Pre | <pre> | Content not escaped (preserves whitespace) |
Blockquote | <blockquote> | Validates cite URL |
Ol | <ol> | Validates type (1/a/A/i/I), start |
Ul | <ul> | |
Li | <li> | Validates value (integer) |
Dl | <dl> | |
Dt | <dt> | |
Dd | <dd> | |
Figure | <figure> | |
Figcaption | <figcaption> | |
Hr | <hr> | Void element |
Text Semantics
| Class | Tag | Notes |
|---|
A | <a> | Validates target, rel, download |
Em | <em> | |
Strong | <strong> | |
Small | <small> | |
S | <s> | |
Cite | <cite> | |
Code | <code> | |
Sub | <sub> | |
Sup | <sup> | |
I | <i> | |
B | <b> | |
U | <u> | |
Mark | <mark> | |
Del | <del> | Validates cite, datetime |
Ins | <ins> | Validates cite, datetime |
Abbr | <abbr> | |
Time | <time> | Validates datetime |
Data | <data> | Requires value attribute |
Var | <var> | |
Kbd | <kbd> | |
Samp | <samp> | |
Form Elements
| Class | Tag | Factory Methods / Notes |
|---|
Form | <form> | Validates method (GET/POST/dialog), enctype, autocomplete |
Input | <input> | 18 factory methods — see below |
Textarea | <textarea> | .new(name, value?), validates rows/cols/wrap |
Select | <select> | Validates size, multiple, required |
Option | <option> | .new(text, value?), validates selected/disabled |
Optgroup | <optgroup> | Validates label (required), disabled |
Button | <button> | .new(text, type?) — defaults to type="button" |
Label | <label> | .new(text, for?) |
Fieldset | <fieldset> | Validates disabled |
Legend | <legend> | |
Datalist | <datalist> | |
Output | <output> | Validates for (space-separated IDs) |
Progress | <progress> | Validates value/max (non-negative) |
Meter | <meter> | Validates value/min/max/low/high/optimum |
Input Factory Methods (all return Input with correct type set):
Input.text("username") Input.email("email")
Input.password("pass") Input.number("qty")
Input.checkbox("agree", "yes") Input.radio("plan", "pro")
Input.submit("Save") Input.button("Click")
Input.hidden("token", "abc") Input.file("upload")
Input.date("birthday") Input.time("alarm")
Input.datetime_local("meeting") Input.range("volume", "0", "100")
Input.color("theme") Input.search("query")
Input.tel("phone") Input.url("website")
Table Elements
| Class | Tag | Notes |
|---|
Table | <table> | |
Thead | <thead> | |
Tbody | <tbody> | |
Tfoot | <tfoot> | |
Tr | <tr> | |
Th | <th> | Validates scope (row/col/rowgroup/colgroup), colspan/rowspan |
Td | <td> | Validates colspan/rowspan |
Caption | <caption> | |
Colgroup | <colgroup> | Validates span |
Col | <col> | Void element, validates span |
Embedded / Media
| Class | Tag | Notes |
|---|
Img | <img> | Void. Validates loading (lazy/eager), decoding, crossorigin |
Video | <video> | Validates controls/autoplay/loop/muted/playsinline, preload |
Audio | <audio> | Validates controls/autoplay/loop/muted, preload |
Source | <source> | Void. Validates MIME type, media queries |
Track | <track> | Void. Validates kind, srclang |
Iframe | <iframe> | Validates sandbox, loading |
Embed | <embed> | Void. Validates MIME type |
Object | <object> | Validates MIME type |
Param | <param> | Void. Requires name/value |
Canvas | <canvas> | |
Svg | <svg> | Content not escaped |
Picture | <picture> | |
Interactive Elements
| Class | Tag | Notes |
|---|
Details | <details> | Validates open (boolean) |