Building or editing any UI surface in Eleblorb (Hud, DialogUI, ShopUI, InventoryUI, PauseMenu, or a new one) through the ui_theme.gd/ui_kit.gd design system. Use before shipping a first pass on new UI, and whenever text is wrapping unexpectedly, a colored element reads illegible, a layout looks unbalanced/stretched, or a panel resizes when it shouldn't.
Install
npx skillscat add pizzapocket/eleblorb/ui-craft Install via the SkillsCat registry.
Eleblorb UI craft: catching first-pass mistakes before they ship
Every UI surface in this project builds through scripts/ui_theme.gd (design tokens) and scripts/ui_kit.gd (builder functions) — that system is already well-documented (see ui_theme.gd's own "Design language" section, which is the authoritative rule list, not this file). This skill isn't a restatement of those rules; it's the specific, recurring set of mistakes that have made it into a first pass anyway despite the rules existing, so they can be checked for deliberately instead of caught by the user after the fact. If a first pass at a UI surface has needed a correction round, the fix belongs in this list once, generalized, so the next surface doesn't repeat it.
Checklist before shipping any new or changed UI surface
Run through this before considering a UI change done — every item below is a mistake that has actually shipped once already:
- Every Label sitting in a flexible row/column has explicit wrap behavior chosen, not left at the default.
caption_label()/body_label()word-wrap by default, and word-wrap has no minimum-width floor — nothing stops the layout from squeezing it down to rendering one character per line the instant its container isn't generously sized. This exact bug has shipped three separate times (a coin count in a header, "Press I to close", and a Blorbs-tab "Type: Normal" field). Useinline_caption()(no-wrap, shrink-to-content) for anything short sitting next to other controls; reserve wrapping labels for text of genuinely unpredictable length inside a block that's guaranteed wide. - Any background color that isn't a fixed palette constant gets its text color derived, not assumed. A tint passed in dynamically (an element color, a per-instance accent) can land anywhere in the lightness range — pairing it with a hardcoded text color works until a light tint shows up (this shipped once: a near-white "Normal" blorb tag with light text on top of it, illegible). Compute contrast from the actual color at build time (see
UIKit._relative_luminance()), don't assume the palette's usual dark-background case. - A text/label column defaults to shrink-to-content (
SIZE_SHRINK_*), notSIZE_EXPAND_FILL. Giving a text blockEXPAND_FILLin a row that also has other elements stretches it across all leftover width, stranding its actual (short) content on one side with a large dead gap before whatever comes next — read as "bizarre and unbalanced" when it shipped. If something in the row needs to visually anchor to the far edge, add a dedicated empty spacerControlwithSIZE_EXPAND_FILLbetween them to absorb the leftover space — don't make content stretch to create positioning. - A layout constant that depends on the panel's own size (column count, row count) is measured, not guessed. A hardcoded
GridContainer.columnstuned by eye at one panel size silently stops making sense the moment the panel's real size changes (this shipped as a fixed 3-4 columns leaving most of a much-wider-than-assumed fixed panel empty). If the container's size is knowable at runtime, connect to itsresizedsignal and compute the count from the real measured width instead of a constant. - Any panel with more than one internal view (tabs, states) has one fixed outer size. Sizing the panel to shrink-wrap its current content (
UIKit.anchor_to_edge()) is right for a single-purpose readout, but visibly resizes/jumps the whole panel every time the internal view changes — wrong for anything with tabs. Use the fixed viewport-relative sizing pattern (set_anchors_preset(PRESET_FULL_RECT)+ equal margins, matching ShopUI/InventoryUI) and put each view's own content in aScrollContainerthat fills the remaining fixed space, so growth scrolls in place instead of resizing the panel. - A new decorative treatment (a border, a frame, a texture) is checked against the design language before adding it, not after. An unrequested wood-bordered "portrait frame" shipped once, directly contradicting the already-documented rule that passive elements are borderless (design language item 1 in
ui_theme.gd). When a new element needs visual weight, reach for an existing stylebox/pattern first (slot_stylebox(),chip_stylebox(), a tint at reduced alpha) rather than inventing a new decorative look — and if a genuinely new pattern really is needed, it goes intoUIKit/UIThemewith a design-language entry, per that file's own instructions, not inline in the calling script. - Color is used to encode a distinct signal, not to decorate something already encoded another way. A per-stat rainbow (STR red, DEF gold, HP green, MP blue) shipped once and read as noise, because the bars' own length already carried the actual comparison — the color added nothing. Before adding color-coding, check whether the thing being colored already communicates the same distinction through position, size, or grouping; if so, one uniform accent color reads cleaner than a rainbow that isn't doing real work.
- A first-pass review actually looks at the real data the surface will show, not just the abstract layout. Both the illegible-tag and the too-narrow-panel mistakes above trace back to reviewing the structure of a change without checking it against the specific values (a near-white default color; a much-wider-than-assumed real panel size) that would have surfaced the bug immediately.
Where the actual rules live
ui_theme.gd's own "Design language" section (currently 11 numbered items) is the source of truth for conventions — borderless passive elements, superellipse shapes everywhere, drawn icons instead of color swatches, the tab-switch pattern, the fixed-size-modal pattern, meters vs. badges for comparable values, and so on. This skill doesn't duplicate that list; it's the failure-mode checklist for enforcing it in practice. If a new mistake makes it into a shipped first pass, add it here as a checklist item (mistake → the specific fix pattern), the same way each item above was added — not as a vague "be more careful," but as a concrete, checkable thing to look for next time.
Related skills
- figure-rig — the player/NPC rig's own axis-and-rotation-sign discipline; a different domain (3D transform math, not 2D layout), but the same underlying practice of recording a confirmed fix once instead of re-guessing it.
- object-photography — rendering a 3D object to a texture for a UI surface to display (portraits, icons); read this one when the content of a UI element is a live-rendered capture rather than a drawn icon or plain text.