| name | apple-platform-guide |
| description | The developer-facing guide to building Apple UI (iOS 26, iPadOS 26, macOS 26) with
asset_pipeline's UI::View system. Backed by the Apple HIG corpus and validated by
macOS + iOS screenshots. Answers "I want to show X — what component, what args,
what HIG says."
|
| user-invocable | true |
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash |
| version | 0.1.0 |
Apple Platform Guide
The developer-facing guide to building Apple UI with asset_pipeline, backed by the
HIG corpus and validated by screenshots. For every Apple HIG component there is a
matching components/<slug>.md answering: what UI::View to use, a quickstart,
the customization knobs, platform differences, and HIG citations. The validation/
tree holds the audit trail (HIG reference + rendered screenshots + prose verdict)
that proves each component doc is honest — it's the quality bar, not the primary
output. The primary output is the usage docs.
Three-tier structure
apple-platform-guide/
SKILL.md (you are here)
foundations/ cross-cutting design concepts, hand-curated
liquid-glass.md
materials.md
hierarchy-and-emphasis.md
color-and-theming.md
typography.md
spacing-and-layout.md
preview-composition.md
preview-screen-recipes.md
accessibility-defaults.md
sound-and-motion.md
components/ one doc per UI::View, generated by the loop
buttons.md (not yet — generated per iteration)
popovers.md
...
recipes/ multi-component patterns, emergent
popover-with-glass.md
...
validation/ audit trail
worklist.json Ralph loop state machine
screenshots/ <slug>-macos.png + <slug>-ios.png
reports/ one markdown verdict per slug
gaps.md human-review queue
README.md
foundations/ is read once. components/ is where you look up how to build X.
recipes/ is for cross-component patterns. validation/ is the audit trail.
Decision-tree lookup: "I want to show…"
Use this table to pick the right starting doc. Descend into the linked component
doc for constructor signatures, theming knobs, and HIG citations.
| Intent | Go to |
|---|
| …a button that triggers an instantaneous action | components/buttons.md |
| …a transient menu from a button (contextual actions) | components/menu-button.md |
| …a focused inspector anchored to an element | components/popovers.md |
| …a full-screen overlay that needs commit/cancel | components/sheets.md |
| …an urgent decision the user must acknowledge | components/alerts.md |
| …a blurred panel behind foreground controls | foundations/materials.md + components/card.md |
| …a Liquid Glass background on a custom view | foundations/liquid-glass.md |
| …a validation screenshot or HIG preview stage | foundations/preview-composition.md |
| …the exact screen to render for a component preview | foundations/preview-screen-recipes.md |
| …vertical or horizontal layout of child views | components/vstack.md / components/hstack.md |
| …a toolbar across the top or leading edge | components/toolbar.md |
| …a scrollable list of items | components/list-view.md |
| …editable single-line text input | components/text-field.md |
| …a selection between 2–5 mutually exclusive options | components/segmented-control.md |
| …a binary on/off control | components/toggle.md |
If your intent isn't in the table, consult ../apple-hig/pages/ (the raw HIG
corpus) to find the closest matching component page, then look up that slug.
Quickstart snippets
Three canonical snippets. Each compiles with crystal-alpha build ... -Dmacos
or -Dios.
1. Button with primary visual weight + glass on iOS 26
UI::Button exposes label, foreground_color, font, disabled, on_tap
(source: src/ui/views/button.cr). A semantic role (:primary | :cancel | :destructive) is planned; today wrap in UI::GlassBackground for primary weight.
require "asset_pipeline/ui"
primary = UI::Button.new("Continue") { puts "tapped" }
primary.foreground_color = UI::Color.new(r: 1.0, g: 1.0, b: 1.0)
primary.font = UI::Font.new(size: 17.0, weight: :semibold)
glass_wrapped = UI::GlassBackground.new(content: primary, material: :regular)
glass_wrapped.corner_radius = 12.0
glass_wrapped.padding = UI::EdgeInsets.new(top: 10.0, trailing: 16.0, bottom: 10.0, leading: 16.0)
2. A popover anchored to a button with thin material
UI::Popover takes a content view and an arrow_edge; the companion
UI::PopoverPresenter owns the anchor-and-present relationship (see
src/ui/views/popover.cr).
content = UI::VStack.new(spacing: 8.0)
content << UI::Label.new("Filter by")
content << UI::SegmentedControl.new(["All", "Unread"], 0)
popover = UI::Popover.new(content: content, arrow_edge: :top)
filter_btn = UI::Button.new("Filter")
presenter = UI::PopoverPresenter.new(popover, anchor: filter_btn)
filter_btn.on_tap = -> { presenter.present }
# The renderer wraps the popover content in a :thin-material glass layer
# on iOS 26 when you set the material explicitly:
glass = UI::GlassBackground.new(content: content, material: :thin)
popover.content = glass
3. A sheet with a toolbar header and confirm/cancel buttons
UI::Sheet carries a content view, detents, and a dismiss callback (see
src/ui/views/sheet.cr). Compose a toolbar header + action row inside the sheet's
content.
header = UI::Toolbar.new("Edit Reminder")
header.add_item("cancel", "Cancel") { presenter.dismiss }
header.add_item("save", "Save") { save_and_dismiss }
body = UI::VStack.new(spacing: 12.0, alignment: UI::Alignment::Leading)
body << UI::TextField.new("Title")
body << UI::DatePicker.new
sheet_content = UI::VStack.new(spacing: 0.0)
sheet_content << header
sheet_content << body
sheet = UI::Sheet.new(content: sheet_content)
sheet.detents = [:medium, :large]
sheet.shows_drag_indicator = true
presenter = UI::SheetPresenter.new(sheet)
presenter.present
Customization layers
There are three places to change how a component looks. Go to the leftmost option
that solves your problem.
-
Per-instance arguments. Constructor args and properties on the
UI::View itself. Example: popover.arrow_edge = :leading,
button.foreground_color = Color.new(...). Always prefer this — it's scoped
to the one place that needs the change.
-
UI::Theme tokens. Global design tokens applied by the renderer to every
component. The full token list lives in src/ui/theme.cr — inspect it directly
for the canonical set. Current tokens include:
- Colors:
primary, on_primary, primary_container, secondary, error,
background, surface, outline (plus on_* pairs for each).
- Typography:
font_family, font_size_body, font_size_title,
font_size_headline, font_size_caption.
- Shape:
corner_radius_small, corner_radius_medium, corner_radius_large.
Presets: UI::Theme.apple_default and UI::Theme.material_baseline. Component-
specific tokens like popover_corner_radius or button_min_hit_target are
planned but not yet in theme.cr — today those values are baked into the
renderer.
-
Renderer metadata. Platform-specific overrides for when a knob doesn't
exist anywhere else. This level means editing appkit_renderer.cr or
uikit_renderer.cr directly. Avoid unless you've checked both of the above and
confirmed there's no abstraction that fits. See platform-renderers skill.
How to ask for a new customization
If you need a knob that doesn't exist:
- Describe the design intent in one sentence. "I want popovers to feel more
formal — darker shadow, tighter corner." Design intent beats API wishlisting.
- Check
foundations/ for a concept that already expresses this. If your
intent is "more formal / quieter", that might already live under
hierarchy-and-emphasis.md or materials.md. Reuse the vocabulary.
- Check
UI::Theme for an existing token. If your change is app-wide
(all popovers, all buttons), a theme token is the right layer — propose adding
one to theme.cr before adding a per-component property.
- If nothing fits, propose in a PR with (a) the design intent, (b) a HIG
citation justifying that it's a real Apple concept not a personal preference,
(c) the proposed API shape, and (d) which layer (instance / theme / renderer)
it lives at.
How to re-validate a component
When a UI::View changes or the HIG page is updated:
- Open
validation/worklist.json.
- Find the row for the slug (e.g.
popovers).
- Flip
validation_state back to "pending" and (optionally) clear
docs_written to force the usage doc to be regenerated too.
- Re-run the Ralph loop with the
apple-platform-designer agent. It will
re-render, re-screenshot, re-compare, and re-write both the report and the
component doc. Previous screenshots are overwritten; previous reports are
replaced.
Cross-references
apple-hig — the raw HIG corpus (166 pages + 1,724 images). Source of truth for
every citation.
ios26-native-components — native SwiftUI / UIKit / AppKit class catalog with
iOS 26 Liquid Glass coverage.
glass-effects — ObjC bridge details for NSVisualEffectView and
UIVisualEffectView. Go here when implementing a new glass-bearing view.
build-ui — the Crystal component DSL for web rendering. apple-platform-guide
is the native-side analogue.
component-api — the full API reference for every UI::View type, record, and
enum.
accessibility — WCAG 2.2 AA compliance — focus, motion, contrast.
ax-test — the macOS accessibility-tree test harness. Used by the validation
loop to confirm rendered components show up in the AX tree.
component-mapping-matrix — the spine table that maps UI::View to SwiftUI /
UIKit / AppKit / Compose / Android / Web. A HIG slug column links rows here
back to components/<slug>.md.
Regeneration
Worklist is generated (not hand-edited) by the triage script:
python3 ../apple-hig/_build/triage.py
That emits validation/worklist.json — one row per HIG component page with the
existing UI::View mapping, status, and priority. The Ralph loop then walks the
pending rows and asks the apple-platform-designer agent to process each:
/ralph-loop:ralph-loop \
"Use the apple-platform-designer agent to validate and document every HIG \
component. Output <promise>HIG_VALIDATION_COMPLETE</promise> when zero pending \
entries remain and every slug has docs_written: true." \
--completion-promise HIG_VALIDATION_COMPLETE
Slash-command syntax: the Ralph loop ships as a plugin skill, so the
command is /ralph-loop:ralph-loop (plugin name : skill name) — not
/ralph-loop alone. Without the colon and second declaration the
command fails with Unknown skill: ralph-loop.
See .claude/agents/apple-platform-designer/agent.md for the per-iteration
contract.