-
Notifications
You must be signed in to change notification settings - Fork 9
Design Three Layer Architecture
Every other doc, every component page, every recipe assumes this picture.
theme JSON ─▶ renderer (XxxRenderer) ─▶ headless (XxxProps) ─▶ gpui-ce
| Layer | Crate | Role |
|---|---|---|
| Headless | yororen-ui-core |
data + state + a11y + i18n. No visual decisions. |
| Renderer |
yororen-ui-default-renderer or yororen-ui-brutalism-renderer
|
turns props into a styled div. One trait impl per component. |
| Theme | a JSON file | palette + design tokens the renderer reads by path. |
The headless layer is the source of truth. The renderer is swappable. The theme is data.
| Concern | Where | Why |
|---|---|---|
| Click handler, focus wiring, keymap | headless::XxxProps |
Same a11y contract for every renderer. |
| Caret, selection, IME, scroll, blink | headless::text_input_core::TextInputCore |
Shared by all 7 text inputs + combo_box. |
| Open / close animation | headless::XxxState::animation: AnimatedVisibility |
Each composite owns its own. |
| bg / fg / border / padding / radius / hover / active | renderer | Themable; the renderer is the only thing that reads cx.theme(). |
| Palette + per-component geometry | theme JSON | Per-deployment, per-brand. |
| Notification queue, sticky flag, auto-dismiss | notification::NotificationCenter |
App-level state, not visual. |
| RTL flip |
rtl + i18n::TextDirection
|
Locale-driven layout adjustment. |
cx.t("key.path") |
i18n::Translate for App
|
Framework + app strings share one resolver. |
The headless layer ships 55 factory functions, each with a marker type in yororen-ui-core::renderer::markers. The default renderer and the brutalism renderer each implement all 55 traits.
Button, ButtonGroup, IconButton, ToggleButton,
Label, Heading, Divider, FocusRing, Badge, Tag,
ProgressBar, Skeleton, Slider, Tooltip, Avatar,
Switch, Checkbox, Radio,
TextInput, TextArea, PasswordInput, NumberInput,
FilePathInput, SearchInput, Select, ComboBox,
Modal, Popover, DropdownMenu, Disclosure,
Toast, Notification, Panel, Card, Form,
ListItem, TreeItem, KeybindingInput, SplitButton,
EmptyState, Image, KeybindingDisplay, ShortcutHint,
Icon, Text, Spacer, Overlay, Menu,
FormField, RadioGroup, Table, Tree,
VirtualList, UniformVirtualList
Every factory exposes the same three ways to consume its props:
| API | Returns | What it does |
|---|---|---|
props.apply(div) |
Stateful<Div> |
Sets id, focus tracking, click handler. No visual feedback. |
props.render(cx) |
Stateful<Div> (or AnyElement for inputs / lists) |
Looks up the registered XxxRenderer, composes the visual, layers a11y on top. |
Hand-roll a gpui::Element
|
anything | You paint; you still call props.apply(...) for a11y. |
When to use which:
-
Default look, fastest path:
.render(cx). The renderer paints bg / border / padding / radius / hover / active from the theme. -
Caller controls every pixel:
.apply(div()).child("Save"). You write the div; the headless layer only contributes the focus handle and the click handler. -
Brand identity or bespoke animation: implement a custom
XxxRenderer, register it once, the registry is global — every.render(cx)in the app picks up your visual.
The layers_demo puts all three side by side in one window.
renderer::install(cx, cx.window_appearance()) is the default — modern rounded look, system-light.json or system-dark.json chosen by OS appearance. Three legitimate reasons to change:
| If you want… | Use |
|---|---|
| A custom theme, but the default look | yororen_ui_default_renderer::install_with(cx, my_theme) |
| A completely different visual (sharp corners, hard shadows) |
yororen_ui::brutalism_renderer::install(cx) (feature-gated) |
| A custom renderer for one component | cx.register_renderer_arc::<markers::Button, dyn ButtonRenderer>(Arc::new(MyButtonRenderer)) |
Swapping default_renderer::install for brutalism_renderer::install flips the entire app's look while leaving every headless call untouched. The same button("save", cx).on_click(...).render(cx) produces a different button — same state, same a11y, different pixels.
Call yororen_ui::theme::install(cx, new_theme) inside a Render impl — every frame, or when the user picks a different palette. The renderer re-reads paths from the new Theme; nothing else needs to change.
Yororen UI v0.3.0 · repository · Apache-2.0 · This wiki documents Yororen UI v0.3.0.
This wiki documents Yororen UI v0.3.0 — the headless-core, swappable-renderer build.