COMPONENTS IN A CAN
SERVER-SIDE HTML COMPONENTS FOR CRYSTAL
COMPILED BEFORE RUNTIME

Can is a server-side HTML component system for Crystal. Write componentized HTML in .can files, compose it with defs, slots, and scoped CSS, then compile it into Crystal before your app runs — no runtime template parser, no client framework.

Can is pre-1.0 and aimed at experiments, small Crystal SSR projects, and developers who want componentized HTML without a browser-side runtime. A template writes HTML to an IO object, so the same components work with Kemal, Grip, plain HTTP::Server, or a small render script.

Components, isolated

Three cards, each defined by a different component. Every one of them uses a .card class internally. They don't collide because each component's CSS is rewritten to require a generated attribute that only its own elements carry. Pop the source on any of them — same class, no juggling.

Soft

Pastel surfaces, rounded corners, a quiet voice.
source
<card-soft title="Soft">Pastel surfaces, rounded corners, a quiet voice.</card-soft>

Bold

High-contrast, monospace heading, a hard drop shadow.
source
<card-bold title="Bold">High-contrast, monospace heading, a hard drop shadow.</card-bold>

Paper

Quiet serif, paper texture, a marginal note.
source
<card-paper title="Paper">Quiet serif, paper texture, a marginal note.</card-paper>

One component, many variants

The same <callout> component renders three different ways, picked by a kind param. Param-driven style is just an expression attribute (class={"callout #{kind}"}) plus a <.if> chain inside the body.

note Server-rendered. No client runtime, no shadow DOM, no build pipeline beyond crystal run.
heads up Slot-bearing components must be defined at class/module scope — Crystal Procs can't take blocks.
danger Use Can.raw() or <.raw> only for content you've already sanitized.
source
<callout kind="info">Server-rendered. No client runtime, …</callout>
<callout kind="warning">Slot-bearing components must be defined …</callout>
<callout kind="danger">Use Can.raw() or <.raw> only for content …</callout>

Made of canned templates

This particular page is one static HTML file produced by crystal run docs/build.cr — every component you see was defined in a .can file and compiled at build time. The same components work unchanged inside a live HTTP handler that writes to env.response; the build script is just one way to consume the rendered output.