Standard of Iron's interface uses the StandardOfIron.Design QML module as the shared presentation layer for colour, spacing, typography, motion, iconography, faction styling, notifications, hints, sound hooks, accessibility-derived values, and reusable controls.
The design system exists to keep the product coherent across battle HUDs, menus, mission screens, settings, save/load panels, overlays, and tooling. Screens are expected to compose shared design primitives and live application state rather than carrying their own independent visual language.
Typography asset rules are documented in TYPOGRAPHY.md. Input and accessibility behavior is documented in ACCESSIBILITY.md.
The module is shipped as a file-based QML resource module:
design_resources.qrc → :/StandardOfIron/Design/...
ui/qml/design/qmldir → module StandardOfIron.Design
Consumers import it with:
import StandardOfIron.Design 1.0 as Design
The qmldir file is the module manifest. It exports the shared singleton tokens and reusable components under ui/qml/design/.
The module boundary matters because QML tooling, runtime imports, tests, and generated type information all need to agree about what the design package contains.
The module can be understood in four layers.
Persistent user preferences come from C++ through UiPreferences.
Singletons such as Theme, Metrics, Typography, and Motion derive effective product values from preferences and accessibility state.
Controls, surfaces, overlays, and layouts consume the tokens and define interaction/presentation behavior.
HUDs, menus, mission screens, settings, save/load panels, and other product views assemble those components around live view-model data.
The intended direction is:
UiPreferences / app state
│
â–Ľ
A11y + design singletons
│
â–Ľ
shared components
│
â–Ľ
product screens
A screen should not need to restate the product's base radius, animation timing, title font, warning colour, or touch-target minimum.
| Singleton | Responsibility |
A11y |
QML-facing accessibility and scaling state |
Theme |
product colours and accessibility-aware semantic colours |
Metrics |
spacing, radii, borders, control dimensions, touch targets |
Typography |
font families, pixel sizes, scale, weights, tracking |
Motion |
durations, easing, dwell timing |
Icons |
shared glyph and painted-art lookup |
ActivityIcons |
action/activity-state icon vocabulary |
Numerals |
numeric/readout presentation helpers |
FactionTheme |
faction accent, heraldry, emblem, motto data |
Notifications |
product-wide notification queue |
UiSound |
UI interaction-sound helpers |
These singletons provide one place for product-wide decisions. They are not gameplay authorities: a theme token can describe how a warning looks, but it does not decide whether a gameplay action is legal.
UiPreferences is the persistent C++ preference object exposed to QML.
The current preference surface includes values for:
This is a broader contract than a handful of appearance toggles. The design system reads the subset that affects visual tokens, while product screens/controllers use the rest for the relevant interaction or display behavior.
A11y.qml mirrors the preference state needed by the design layer.
Metrics, Typography, Motion, and Theme then derive effective values from that state.
For example:
This keeps accessibility behavior product-wide. A new screen using the shared tokens inherits the same scaling and contrast rules instead of requiring a bespoke accessibility implementation.
Metrics.qml owns reusable geometry such as:
The value of tokenizing these measurements is not only visual consistency. It also makes layout respond coherently to interface scale.
A screen that hard-codes a local 28 px button while the rest of the UI derives control height from scaled metrics can become both visually inconsistent and harder to use at large interface scales.
Text uses pixel-size values from Design.Typography instead of arbitrary point sizes or screen-local literals.
The type scale provides named rungs for:
Lower text rungs respect Typography.minimumSize, and interactive geometry respects Metrics.minTouchTarget.
Typography is therefore a combination of font family, scale, minimum legibility, tracking, and context—not simply a font-size constant.
scripts/check-typography.py enforces the tokenized typography rules in QML. See TYPOGRAPHY.md for bundled font ownership and glyph coverage.
The product ships its own display face for title/brand usage and a bundled text fallback for broader script coverage.
The design system resolves those families through Typography instead of allowing screens to choose arbitrary host fonts.
That is especially important for screenshots, releases, localization, and promo rendering: the product should not reflow simply because the operating system happens to have a different font installed.
Theme.qml provides the shared product palette and semantic colours.
Screens should prefer semantic intent—such as emphasis, warning, danger, success, panel surface, text hierarchy, or disabled state—over embedding raw colour literals.
The semantic layer allows accessibility variants to change the effective value while the component retains its meaning.
For example, a “danger” action can remain the danger semantic whether the active palette changes for high contrast or colour-vision accessibility.
FactionTheme.qml maps faction/nation identity to accent, heraldry, emblem, and motto presentation.
Faction styling changes the skin of shared components rather than their interaction contract.
A button remains a button; a notification remains a notification; a mission card keeps the same spacing and hierarchy. Faction identity changes emphasis and branding without requiring each nation to maintain a second component library.
This is important for accessibility as well: nation identity is not supposed to be communicated only through hue. Heraldry, iconography, text, and team-pattern systems provide additional channels.
Motion.qml owns durations, easing, and dwell behavior.
Motion has at least two distinct purposes:
Reduced-motion mode can shorten or eliminate decorative movement without also making text disappear immediately.
Components should therefore consume Motion tokens rather than assuming that “reduced motion” means every timeout becomes zero.
Product icons are resolved through Icons.qml, ActivityIcons.qml, and the related numeral/art helpers.
The design system avoids arbitrary emoji or platform-dependent symbols in product screens.
Shared icon families provide:
Activity icons encode meaning through shape/semantics in addition to colour so they remain useful under colour-vision adjustments.
Numerals centralizes product-specific numeric presentation.
This is useful for interfaces that mix Roman-themed visual language with ordinary numeric quantities. Screens can present counts and labels consistently without reimplementing formatting rules in each panel.
Gameplay values still come from application/simulation state; Numerals controls representation, not the value itself.
Reusable components live under design subdirectories for controls, surfaces, overlays, and layouts.
A useful mental model is:
Screens should assemble these primitives before introducing a local one-off component with nearly identical behavior.
A reusable control should make its important states visually distinct and accessible.
Typical state dimensions include:
The product frequently needs to explain why a gameplay action is unavailable, so disabled styling alone is not enough for command surfaces.
IronCommandTooltip is the detailed explanation surface used by command-grid actions.
It can present:
The critical data rule is that gameplay numbers and refusal reasons come from application/view-model action state rather than copied QML balance constants.
A tooltip may explain “missing 15 stone,” but the amount should come from the same gameplay/economy source that will accept or reject the command.
The UI distinguishes an unavailable command from an unexplained disabled control.
Where the application can expose a reason, the command surface can show the reason directly in tooltip/status text.
This pattern is used throughout gameplay UX because a refusal such as:
is actionable information for the player.
The design system provides the presentation vocabulary; simulation/application logic provides the reason.
Design.Notifications is the shared product-wide notification queue.
Notifications use ordered priority bands:
critical;urgent;info;ambient.Within one band, entries retain FIFO order.
This keeps a low-priority ambient notice from displacing a combat-critical warning merely because it was pushed one frame later.
A notification can carry a channel.
Repeated pushes on the same channel are coalesced into the existing pending entry, with repeat count and priority handling performed by the queue.
Channel coalescing is important for high-frequency events. A repeated warning should not fill the entire notification stack with identical cards if the product can instead update one existing entry.
Sticky notifications require explicit dismissal. Ordinary entries use dwell timing from Motion.
Product code publishes notifications; NotificationHost renders the current queue for the owning shell.
That separation means gameplay/application code does not need to know where on screen a notification card sits or how it animates.
It publishes message semantics and priority; the design system presents them.
Persistent coaching is managed through the C++ UiHints registry rather than a collection of unrelated screen-local “seen” booleans.
The registry distinguishes:
Supported operations include:
HintCard.qml is the common presentation shell.
The registry owns identity/persistence policy. The product screen owns the world-state condition that decides whether the hint is relevant right now.
Settings consume the registered hint catalogue rather than maintaining a separate hard-coded list.
This keeps “what hints exist” consistent between runtime coaching and preference controls.
A newly registered hint can therefore become configurable without requiring another independently maintained settings model.
Attack and interaction modes use the same design language as the command UI.
World cursor/marker feedback, command-panel state, tooltip status, and selected mode are meant to agree.
Targeting visuals are gated by active command mode so ordinary hover does not become a second implicit attack/interaction mode.
The world feedback communicates the simulation/application decision; it does not independently decide target legality.
Selection/inspection UI follows the same hierarchy rules as command panels:
Large selections can use grouping/aggregation, but the design system still supplies consistent typography, spacing, state treatment, and interaction components.
The battle HUD combines several independent subsystems:
The design system keeps these regions visually related while allowing each subsystem to own its live data.
The HUD should not become a monolithic QML object that reimplements resource, combat, formation, or mission rules merely because it displays all of them.
The same tokens and controls are used outside battle for:
Using one module across gameplay and menu UI prevents the product from having a “battle visual language” and a different “menu visual language” with inconsistent control metrics and interaction states.
Arabic uses right-to-left interface support.
Reusable components should avoid assuming that left-to-right placement is universal. Text alignment, directional layout, ordering, and icon relationships need to cooperate with the product's RTL behavior.
The design system provides common building blocks, while individual screens remain responsible for using mirroring-safe layout patterns where needed.
UiPreferences includes focus-visibility behavior, and reusable controls should expose keyboard focus consistently.
This is especially important because gameplay commands, menus, settings, and dialogs mix mouse and keyboard use.
A control that can receive keyboard activation should not hide its focus state merely because the default pointer workflow did not need it.
Team identity can use both colour and pattern.
The team-pattern preference exists so rings/markers can communicate identity without relying solely on hue. Shared components and world overlays should consume the application/design identity state rather than inventing local team colours.
UiPreferences carries presentation preferences for damage/economy numbers.
These options affect whether/how certain feedback is shown, not the authoritative values themselves. The simulation still owns damage and resources; UI settings decide whether those values are surfaced in a particular visual form.
UiSound centralizes shared UI interaction-sound helpers.
Buttons/screens should use the common interaction sound path where appropriate instead of choosing unrelated assets or playback behavior locally.
Gameplay audio remains owned by the gameplay/audio systems; UiSound is for interface interaction feedback.
ComponentGallery.qml and GalleryWindow.qml provide a dedicated review surface for the design system.
The gallery is useful for checking:
without navigating through a full mission.
A shared gallery is especially valuable for regression review because it exposes many component states together.
Arena and the map editor use Qt Widgets rather than the QML control set.
They still use shared C++ theme/font support and bundled product font assets so they remain visually related to the main application.
The implementation widgets differ, but the product palette/font sources remain shared where the toolkit boundary permits it.
The design system can present gameplay state but does not own it.
Examples:
This boundary is what keeps QML from becoming a shadow gameplay implementation.
Several repository checks protect presentation consistency.
Examples include:
The exact checks evolve with the code, but the architectural expectation remains: shared presentation rules should live in shared design/pref sources, not be copied into screens.
The UI/design system is covered by:
tests/ui/qml/;QML tests are particularly useful for stateful components where visual correctness depends on live bindings, focus, selection, or accessibility settings rather than static appearance alone.
When adding or changing a screen, the preferred sequence is:
A local component is justified when it represents a genuinely screen-specific concept, not simply because recreating a shared button with local rectangles/text feels faster.
The current UI design system depends on these invariants:
| Concern | Source |
| Design module | ui/qml/design/ |
| Module manifest | ui/qml/design/qmldir |
| QML resources | design_resources.qrc |
| Persistent preferences | ui/preferences.h and implementation |
| Design tests | tests/ui/qml/ and related C++ tests |
| Typography checks | scripts/check-typography.py |
| Font architecture | TYPOGRAPHY.md |
| Input/accessibility | ACCESSIBILITY.md |
The design system is defined by the current StandardOfIron.Design module and the preference/application state that feeds it. Historical issue notes about how individual controls were introduced are not part of the present design contract.