half-built-ui

About

This page shows every component in the half-built design system. The system was extracted from half-built-robots.com. That site uses these packages. All three packages are published on npm under the MIT license.

The palette editor in the corner changes the theme variables live. It also prints the current values as a block that can be pasted into a stylesheet. The block holds every theme value. Eight are brand colors and four are code viewer colors. Syntax highlighting reads the same variables, so the code and its background change with the palette.

npm install @half-built/css @half-built/astro @half-built/tooling

Frame

The header and footer come from the package and contain no site-specific values. The site name and other identity details are passed in as typed props. The theme toggle is in the header, and a second copy is in the palette panel next to the color pickers. All toggles on a page stay in sync.

Show imports
import Masthead from "@half-built/astro/components/Masthead.astro";
import Footer from "@half-built/astro/components/Footer.astro";
import ThemeToggle from "@half-built/astro/components/ThemeToggle.astro";
import TwoColumn from "@half-built/astro/components/TwoColumn.astro";

Header title and search

The header's title slot replaces the text inside the home link. The centering, the boxed tagline, and the heading level stay with the package, and the slotted element's own class carries any font change. With the slot empty, siteName renders as plain text. Passing searchAction={false} removes the search button, flyout, and form from the header. This site does that, because it has no search page.

---
import Masthead from "@half-built/astro/components/Masthead.astro";
---

<Masthead
  siteName="Sample Site"
  tagline="A sample tagline"
  nav={nav}
  socials={socials}
  dateStr={dateStr}
  searchAction={false}
>
  <span slot="title" class="wordmark">Sample <em>Site</em></span>
</Masthead>

Two-column layout

TwoColumn is the page row. It holds the main column and a sidebar. The sidebar takes 30% of the row and stacks under the main column at the sidebar breakpoint. The frame below stands in for the page.

This is the main column. It holds post bodies, archives and static pages. Its width is whatever the sidebar leaves.

A second placeholder paragraph gives the column some length next to the sidebar.

Cards

PostCard is the card used on archive pages. The second card here is marked as a draft. The draft badge lets an unfinished post be published while still labeled as unfinished. The third card has both corner badges. One marks an AI-generated image and the other marks a post with an interactive demo. CategoryCard is a smaller tile. It shows an image with the category name on a chip over its bottom edge.

A card's text is taken from the post body. In the Derived excerpt example below, the text starts after an ExcerptStart marker. The aside above the marker is left out.

Show imports
import PostCard from "@half-built/astro/components/PostCard.astro";
import CategoryCard from "@half-built/astro/components/CategoryCard.astro";
import { deriveExcerpt } from "@half-built/astro/lib/excerpt.ts";
import ExcerptStart from "@half-built/astro/content/ExcerptStart.astro";

Post cards

Derived excerpt

The sample post body is passed through deriveExcerpt to produce the card's text. Text before the marker is not included.

A short aside that opens the sample post and
should stay off the card.

<ExcerptStart />

The card text starts here, after the marker. It runs until the
excerpt limit, where it is cut at a word boundary with an ellipsis.
2 min read
Sample

Sample post, excerpt after a marker

The card text starts here, after the marker. It runs until the excerpt limit, where it is cut at a word boundary with an ellipsis…

Category cards

Components

This section covers the rest of the package, including navigation and the blocks used in post bodies. Page-level components are imported from components/ and post body blocks from content/. The split only reflects where the files live. No component in the system caps the width of text. Text width comes from the layout's column.

Show imports
import Pagination from "@half-built/astro/components/Pagination.astro";
import PostNavigation from "@half-built/astro/components/PostNavigation.astro";
import Widget from "@half-built/astro/components/Widget.astro";
import LinkListWidget from "@half-built/astro/components/LinkListWidget.astro";
import Subscribe from "@half-built/astro/components/Subscribe.astro";
import Button from "@half-built/astro/content/Button.astro";
import Callout from "@half-built/astro/content/Callout.astro";
import EditorNote from "@half-built/astro/content/EditorNote.astro";
import WhenPublished from "@half-built/astro/content/WhenPublished.astro";
import PostLink from "@half-built/astro/content/PostLink.astro";
import Group from "@half-built/astro/content/Group.astro";
import CodeBlock from "@half-built/astro/content/CodeBlock.astro";
import Spacer from "@half-built/astro/content/Spacer.astro";
import Quote from "@half-built/astro/content/Quote.astro";
import Walkthrough from "@half-built/astro/content/Walkthrough.astro";
import Step from "@half-built/astro/content/Step.astro";
import Gallery from "@half-built/astro/content/Gallery.astro";
import GalleryImage from "@half-built/astro/content/GalleryImage.astro";
import BlogImage from "@half-built/astro/content/BlogImage.astro";
import MediaText from "@half-built/astro/content/MediaText.astro";
import Popout from "@half-built/astro/components/Popout.astro";

Pagination

Pagination is the numbered row at the bottom of an archive page. It renders nothing when there is only one page.

Post navigation

PostNavigation is the pair of links at the end of a post. The newer post is on the left and the older post is on the right. This matches the newest-first order of the archives.

Link list widget and Widget

Widget is a heading above arbitrary content. LinkListWidget is a Widget filled with a list of links and their counts.

Sample links

Plain widget

A Widget with arbitrary content in its body instead of a link list.

Stat ledger

StatLedger sets a few figures side by side in one ruled box, a label over each value. One figure can take the accent. It is a description list, so a screen reader pairs each label with its value, and it stacks into one column at phone width.

Sample A
1,024 units
Sample B
512 units
Ratio
50.0%

Meter

Meter shows how far a count has come toward its total, in ruled segments. It carries role="meter" with the value and a text reading, and it needs no script. The value is clamped to the range, so an overshoot fills the bar and a total of zero leaves it empty. A missing value, which is not a finite number, reads as empty too.

Popout

Popout holds a short note behind a cell in a data table. It is for detail that is worth keeping but does not need its own column. On a wide screen it opens below its trigger, or above it when there is no room below. It closes automatically when the trigger scrolls out of view, when keyboard focus leaves it, or when the window crosses into phone width. On a phone it opens as a sheet from the bottom of the screen, with the row name as its caption. The layout loads popout.css and calls mountPopouts once.

Row Value Notes
Row one 42
Row two 7
Row three 19
---
import "@half-built/css/popout.css";
---

<script>
  import { mountPopouts } from "@half-built/astro/scripts/popout.ts";

  mountPopouts(document);
</script>
---
import Popout from "@half-built/astro/components/Popout.astro";
---

<Popout label="Row one">
  A note can contain a <a href="#components">link</a> and
  <code>inline code</code>.
</Popout>

Path player

The path player opens in the plate modal and adds playback controls and a timeline. The caller supplies the samples, the tracks under the stage, and the stage itself. The stage in this demo is a point moving on a closed curve. When reduced motion is set, the player opens paused.

---
import "@half-built/css/plate-modal.css";
import "@half-built/css/path-player.css";
---

<script>
  import { createPathPlayer } from "@half-built/astro/scripts/path-player.ts";

  const player = createPathPlayer(document, {
    title, source, caption, duration, samples, tracks,
    buildStage: (viewbox) => { /* append the stage, return true */ },
    sink: { start, move, stop },
  });

  button.addEventListener("click", () => player.open(button));
</script>

Plate frame

The plate frame is the plate modal's frame drawn inline: four accent corners around a plate with the surface and shadow. An icon box with .plate-frame-corner sits on the plate's top-right corner, where the modal puts its close box, and can open the full view. The corners come from the outer box's background, so the markup adds no elements for them. At phone width the corners drop and the box moves inside the plate, as in the modal.

Placeholder sample, a wide geometric figure
<div class="plate-frame">
  <div class="plate-frame-plate bracket-frame">
    <button type="button" class="icon-box plate-frame-corner" aria-label="Enlarge sample">
      <Icon name="maximize-2" size={16} strokeWidth={2} />
    </button>
    <!-- content -->
  </div>
</div>

Subscribe

Subscribe is a standard form with a method and an action, so it submits with JavaScript turned off. Its text is in named slots and can be replaced without forking the component. Its status lines can be replaced through the messages prop.

Button

Button has four variants and two sizes.

A single call to action uses the centered row.

Centered call to action

Callout

Callout sets a passage apart from the body text. It is used for caveats and warnings the reader should not skip. The type sets the label and the line color.

note
A note callout. This is the default.
info
An info callout. It is graphite like the note. Only the label is different.
Tip
A tip callout in the first accent.
warning
A warning callout, also in the first accent.
success
A success callout in the second accent.
error
An error callout in red.

Editor's note

EditorNote is a note to the author that only renders on a dev server. A production build outputs nothing for it, so it can stay in published content as a reminder. This one is forced visible with the shown prop.

Editor's Note: Demo
A demo note, forced visible. It is the only component in the system with a dashed rule.

When published and Post link

Both components take the site's post collection as a prop and do not read any content themselves. PostLink resolves a slug to the post's dated URL at build time. While the post is a draft, it renders plain text instead of a link. WhenPublished renders its content only after the named post is published. Its optional fallback slot renders until then. The sample collection here has one published post and one draft.

This passage is visible because its post is published. It links to the sample post. The URL was resolved from the slug.

This fallback stands in while the draft is unpublished.

Group

Group is a boxed prose container. It uses the same box as Callout without the label or tint.

Code block

def read_ir(code):
    return "power" if code == 0x40BF else "unknown"

Spacer

A fixed-height spacer sits below this paragraph.

Normal flow resumes here, after the spacer.

Quote

A quoted line, with an attribution footer.
~ Demo Author, 2026

Walkthrough and Step

  1. First step. One placeholder photo beside its text.
  2. Second step. The step number is on a chip in the corner of the image.

Gallery

Blog image

Wide placeholder image AI Art
A full-width figure with a caption and the AI Art badge.

Media text

MediaText puts media on one side and prose on the other. This is the caption slot. It holds a short paragraph rather than a single line. On a phone the image stacks above the prose, which is centered like a figure caption.

CSS primitives

The tokens are the CSS variables that the rest of the system reads. Every color on this page comes from them. The palette editor changes the page by setting twelve of them. Each value is tuned to pass WCAG AA against its background. The prose and pattern sheets cover paragraph spacing and the boxed styles.

Show imports
import "@half-built/css/tokens";
import "@half-built/css/prose.css";
import "@half-built/css/patterns.css";
import Palette from "@half-built/astro/content/Palette.astro";

Table

Every table in the system uses this boxed style. Wide tables scroll inside their own box, so the page never scrolls sideways.

Breakpoint Query What changes
--bp-phone max-width: 768px The header's top band is hidden and the footer groups collapse.
--bp-sidebar max-width: 991px The two-column row stacks.
--bp-tablet max-width: 1024px Images load a file sized to the screen width instead of the 990px column.

This wider table scrolls in its own box. It lists every color primitive in the sheet, with one row per family and one column per stop.

Family plain075100200300400500600700750800850900vivida10a55a60
white #ffffff rgba(255, 255, 255, 0.1)
gray #ececec #efefef #d9d9d9 #cccccc #9a9a9a #555555 #404040 #3a3a3a #2a2a2a
black #1a1a1a #111111 rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.6)
graphite rgba(48, 48, 48, 0.55)
ink #3b2e1e #1b140c
parchment #e8d9c3
brand-1 #ffd18a #ffaa3c #d1820f #a36300 #e07c14
brand-2 #8ee6f2 #3cc7dd #1a7f90
red #ff8a7a #7a1f16
violet #b39ad6 #7a5aa0
umber #8e7e67

Tokens

The token roles that components read, with the value of each in the current theme and palette.

Swatch Role Hex Used for
--ink Body text and rules
--surface The page ground
--rule Borders and frames
--accent-1 Accent 1 lines and fills
--accent-1-ink Accent 1 as readable text
--accent-2 Accent 2 lines and fills
--accent-2-ink Accent 2 as readable text
--error-ink The error callout's line and label
--code-token-function Function names in code
--code-token-string Strings and constants in code

Type scale

The fluid type sizes. Each line is set at its own size and shows the token name.

  • --font-size-xs Sample text
  • --font-size-sm Sample text
  • --font-size-base Sample text
  • --font-size-md Sample text
  • --font-size-lg Sample text
  • --font-size-xl Sample text
  • --font-size-xxl Sample text

Prose specimen

Placeholder body copy with inline code and a placeholder link.

A placeholder blockquote in a gray box.
  • Placeholder list item one
  • Placeholder list item two

A sentence with a footnote reference.1 A claim with three sources stacked.234

Footnotes

  1. A placeholder footnote with the return link that Markdown footnotes include. ↩

  2. A placeholder first source. ↩

  3. A placeholder second source. ↩

  4. A placeholder third source. ↩

Patterns

Specimen Class Used for
Chip .chip Filled label. Used for the read-time chip and the category card's name.
Boxed label .boxed-label Bordered box, usually placed across a frame rule. Used for the header's date box and widget titles.
.icon-box Bordered square button. Used for social icons, the search button and the palette panel's close button.
Micro label .micro-label Small bold text with wide letter spacing. Used for plate labels and the lightbox's HOME box.
Caption text, small and muted. .caption The small line under an image, beside media text or after a quote.