Skip to content

Latest commit

 

History

History
280 lines (222 loc) · 7.99 KB

File metadata and controls

280 lines (222 loc) · 7.99 KB
icon lucide/palette

Styling And Themes

Animated GIF showing Glyph theme colors, variants, and state styles.

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.

Inline Style

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:

  • background
  • color
  • borderColor
  • borderWidth
  • radius
  • lineWidth
  • font
  • fontSize
  • lineHeight
  • opacity
  • shader
  • blendMode

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.

Typography

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

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:

  • hover
  • pressed
  • focused
  • active
  • disabled

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).

Themes

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()

Variants

Use variant to select component theme variants:

ui.button({
  label = "Save",
  variant = "primary",
})

Precedence

A node's draw style is resolved by merging sources in order, later wins:

  1. theme.base
  2. theme.components[type] — component defaults (e.g. button, input)
  3. the selected variant (theme.components[type].variants[variant])
  4. component state styles for the active states
  5. variant state styles for the active states
  6. legacy top-level props (background, color, radius, …)
  7. inline props.style
  8. inline props.style state 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.

Audio Cues

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.

Style Helpers

  • ui.style(table)
  • ui.variant(name, table)
  • ui.composeStyles(...)

Transitions

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.

Feedback Vs Transitions

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.