| icon | lucide/palette |
|---|
Tip
See it in action: examples/themes swaps full themes, and
examples/styles shows variants and state styles.
Glyph uses Lua tables for styling, not CSS syntax.
ui.button({
label = "Run",
style = {
background = { 0.1, 0.5, 0.9, 1 },
color = { 1, 1, 1, 1 },
borderColor = { 1, 1, 1, 0.2 },
borderWidth = 1,
radius = 4,
},
})Supported visual fields include:
backgroundcolorborderColorborderWidthradiuslineWidthfontfontSizelineHeightopacityshaderblendMode
Existing compatibility props such as backgroundColor, borderColor, color, and radius still work, but style is preferred.
Note
Layout fields such as width, height, padding, margin, gap, align,
and justify belong on the component props table, outside style. Keep
style for visual fields and interaction-state styles.
Themes can define a font registry, typography presets, and a global text scale:
ui.setTheme({
textScale = 1.15,
fonts = {
body = { path = "assets/ui.ttf", filter = "linear" },
heading = { path = "assets/ui-heading.ttf", filter = "linear" },
japanese = { path = "assets/DotGothic16-Regular.ttf" },
},
fontFallbacks = { "japanese" },
typography = {
text = { font = "body", fontSize = 14, lineHeight = 20 },
h1 = { font = "heading", fontSize = 30, lineHeight = 36 },
caption = { font = "body", fontSize = 11, lineHeight = 15 },
pixel = { font = { path = "assets/pixel.ttf" }, fontSize = 12, fontFilter = "nearest" },
},
})font may be a Love2D font object, a registered font name, or a font spec table
such as { path = "assets/ui.ttf" }. A spec may also use source for a
Love-supported in-memory source such as FileData. Specs are instantiated at
the resolved fontSize after textScale, then cached by source, size, hinting,
and filter. Prebuilt Love Font objects remain fixed at the size at which the app
created them; use a spec when one family must serve several presets. Text defaults to
fontFilter = "nearest" for crisp pixel scaling; set fontFilter = "linear" or
use a font spec with filter = { min = "nearest", mag = "linear" } when a
specific text style needs different Love2D filtering.
Use fontFallbacks when plain text may contain glyphs outside the selected
font. Glyph asks fonts that implement Love2D's hasGlyphs whether they can draw
the text; if the selected font cannot, it uses the first fallback that can. A
text node or typography preset may also set fontFallbacks to override the
theme order for that label.
Text nodes select presets with textStyle:
ui.text("ALERT", { textStyle = "h1" })
ui.richText("[font=mono]Optional SYSL text[/font]")State styles are nested tables:
style = {
background = { 0.1, 0.1, 0.12, 1 },
hover = { background = { 0.16, 0.16, 0.2, 1 } },
pressed = { background = { 0.08, 0.08, 0.1, 1 } },
focused = { borderColor = { 0.4, 0.7, 1, 1 } },
disabled = { opacity = 0.5 },
}Supported states:
hoverpressedfocusedactivedisabled
button, input, and tab ship a default disabled style (a muted
background and text), so setting disabled = true dims them without any per-app
styling. Override the theme component's disabled table to customize it.
Their default focus styles strengthen the existing boundary to two pixels so
keyboard and gamepad focus remains distinct without changing control geometry.
Tabs should use active state styling rather than ad hoc active colors.
Note
textAlign ("left" | "center" | "right") aligns text within a node. Do
not confuse it with the flex align prop, which controls cross-axis alignment
of a container's children (see Layout).
Glyph's built-in theme is intentionally utilitarian: blackened neutral surfaces, warm text, graphite rules, a single amber action color, and corners that are square or only slightly eased. This keeps tool screens and HUDs from turning every group into a rounded card. Applications can replace every token; the defaults are a legible starting point, not a required house style.
Set accentColor to change the primary action color. Glyph derives matching
hover and pressed colors when only that token changes; set
accentHoverColor or accentPressedColor when the palette needs exact state
values. Component-level variant states still take precedence.
Set a theme globally:
ui.setTheme({
textColor = { 0.92, 0.92, 0.96, 1 },
components = {
button = {
background = { 0.12, 0.12, 0.16, 1 },
variants = {
primary = {
background = { 0.1, 0.5, 0.9, 1 },
color = { 1, 1, 1, 1 },
},
},
},
},
})Read the current theme:
local theme = ui.getTheme()Use variant to select component theme variants:
ui.button({
label = "Save",
variant = "primary",
})A node's draw style is resolved by merging sources in order, later wins:
theme.basetheme.components[type]— component defaults (e.g.button,input)- the selected
variant(theme.components[type].variants[variant]) - component state styles for the active states
- variant state styles for the active states
- legacy top-level props (
background,color,radius, …) - inline
props.style - inline
props.stylestate styles for the active states
So inline style overrides the theme, and a node's own state style (e.g.
style = { hover = {...} }) overrides the component/variant state style. When
several states are active at once, they apply in the order hover → pressed →
focused → active → disabled, so disabled wins over active, which wins over
focused, and so on.
Resolved styles are cached per node and invalidated when the node's state, the
inputs, or the theme version change.
Glyph can resolve UI audio cue names from theme components, variants, and node props. Glyph only emits cue events; your app owns Love2D sources and playback.
ui.setTheme({
components = {
button = {
audio = {
hover = "ui-hover",
press = "ui-press",
activate = "ui-activate",
focus = "ui-focus",
},
variants = {
danger = {
audio = { activate = "danger-confirm" },
},
},
},
},
})Per-node audio overrides the theme, and audio = false silences that node:
ui.button({
label = "Silent",
audio = false,
})
ui.button({
label = "No confirm sound",
audio = { activate = false },
})Listen with ui.on("audio", handler) and play app-owned sources there.
ui.style(table)ui.variant(name, table)ui.composeStyles(...)
Style transitions are lightweight interpolation tables:
style = {
background = { 0.1, 0.1, 0.12, 1 },
hover = { background = { 0.2, 0.2, 0.26, 1 } },
transition = { background = 0.12, opacity = 0.08 },
}Style transitions should only mark style dirty unless animating layout fields.
For mount/unmount motion, use node enter and exit animations instead. Those
animations are visual transforms powered by Glyph's vendored flux runner and do
not affect layout or input geometry.
Use state styles for steady interaction appearance, such as hover colors or
focused borders. Use style.transition to interpolate those style fields.
Use enter / exit for lifecycle motion when nodes mount or unmount.
Use ui.feedback for triggerable game-feel stacks such as squash/stretch on
press, a pop on activation, audio cue metadata, or app-owned particle/shake
events. Feedback animation is visual-only and composes with node enter/exit
animation during drawing.
