Luna's UI Effects system adds composable shader-based visual effects to any UI Toolkit VisualElement — drop shadows, glows, insets, outlines, gradients, shine sweeps, flash pulses, tiled patterns, dissolves. Authoring is USS-driven: an element opts in with the luna-fx marker class, then tier / body / fill classes layer the look. C# exists as an imperative escape hatch but isn't the recommended path.
<!-- A shadowed, glossy yellow button -->
<engine:Button class="luna-fx luna-fx-btn luna-fx-clay luna-fx-yellow"
text="Forge"/><!-- A card with a soft drop shadow -->
<engine:VisualElement class="luna-fx luna-fx-shadow-md"/><!-- Pulse-glowing chip -->
<engine:VisualElement class="luna-fx luna-fx-glow-md luna-fx-pulse"/>If you have LunaUIManager in the scene with Auto Install Fx enabled (the default), every UIView automatically wires its descendants — no extra setup. The first class is always luna-fx; everything else is composable.
Eight effects, all running through one Shader Graph uber shader. Each is keyword-toggled; disabled effects compile out and cost nothing.
| Effect | Class hint | Use it for |
|---|---|---|
| Outer | luna-fx-shadow-*, luna-fx-glow-*, luna-fx-ring | Drop shadow, halo glow, colored rim |
| Outline | (auto in default stack) | Crisp border, dashed/dotted/circling/shine border |
| InnerOverlay | luna-fx-inset-*, luna-fx-track, luna-fx-rim-bottom | Recessed/embossed look, claymorphic depth, side-mask rims |
| Gradient | luna-fx-clay, luna-fx-bar, luna-fx-matte, luna-fx-rainbow, luna-fx-aurora, luna-fx-holo, … | Surface fill, linear/radial CSS gradients |
| Shine | luna-fx-shine | Animated sweeping highlight (linear or radial) |
| Flash | luna-fx-pulse | Periodic full-element flash/breathe |
| Tile | --luna-fx-tile-image | Tiled texture overlay (animated, with reveal/coverage) |
| Dissolve | --luna-fx-dissolve-rate | Edge-banded dissolve / appear / disappear effect |
The default stack always includes Outer + Outline. The other six are lazy-stacked the first time their marker variable resolves on the element — so a card that only uses luna-fx-shadow-md never pays for the Shine or Tile slots.
Ship with Luna out of the box. Use them as building blocks; combine freely.
| Class | Effect |
|---|---|
luna-fx | Required. Opts the element into the system. |
Outer effect. Six tier sizes per family.
| Class family | Sizes | Use |
|---|---|---|
luna-fx-shadow-* | xs, sm, md, lg, xl, 2xl | Black drop shadow under the element |
luna-fx-glow-* | xs, sm, md, lg, xl, 2xl | Palette-colored halo (pulls from --luna-fx-grad-tone-2) |
Glow tiers also bump strength on :hover automatically.
InnerOverlay effect. Six tier sizes.
| Class | Use |
|---|---|
luna-fx-inset-xs … luna-fx-inset-2xl | Recessed/embossed inner shadow on all four edges |
luna-fx-rim-bottom | Side-mask modifier — pair with an inset tier; only the bottom rim renders. Signature "card lip" depth. |
Each body bundles outer + inner + outline overrides for a typical UI shape.
| Class | What it is |
|---|---|
luna-fx-btn | Tight drop shadow + bottom-heavy inset, with built-in :hover / :active / :disabled state values |
luna-fx-track | Recessed rail (top-heavy inset) — progress-bar bg, slot bg |
luna-fx-banner | Bottom-edge inset + soft cast — modal headers, accent strips |
luna-fx-ring | Palette-colored halo + thin inner highlight — portrait frames |
Gradient effect. Each sets --luna-fx-gradient to a CSS gradient string referencing --luna-fx-grad-tone-1/2/3. Always combine with a color binding (e.g. luna-fx-yellow) so the tones resolve.
| Class | Look |
|---|---|
luna-fx-clay | 4-stop vertical "puffy clay" |
luna-fx-bar | 3-stop horizontal with end-cap (progress fills) |
luna-fx-matte | Near-flat 2-stop |
| Class | Effect |
|---|---|
luna-fx-shine | Animated sweeping highlight |
luna-fx-pulse | Flash/breathe pulse |
The Essentials sample ships extra opinionated presets in UIEffect_Flair.uss:
luna-fx-rainbow, luna-fx-sunset, luna-fx-aurora, luna-fx-holo, luna-fx-candy, luna-fx-orb, luna-fx-spotlight, luna-fx-vignetteluna-fx-rainbow-outline, luna-fx-candy-outline, luna-fx-gold-outline, luna-fx-silver-outline, luna-fx-bronze-outlineluna-fx-scan, luna-fx-stripes, luna-fx-dots, luna-fx-hearts, luna-fx-stars, luna-fx-dashed-10, luna-fx-dashed-20These are sample-side; copy what you like into your project's USS.
The full --luna-fx-* vocabulary, by effect. Every variable supports var(), :hover / :active / :disabled selectors, and theme tokens. Suffixed variants (-hover, -active, -disabled) on the animatable scalars are picked up by the auto-attached state-tween for smooth transitions.
| Variable | Type | Notes |
|---|---|---|
--luna-fx-outer-strength | float | 0 = dormant. Animatable. |
--luna-fx-outer-width | float | Pixel reach. Animatable. |
--luna-fx-outer-edge-spread | float | Soft falloff from element edge. |
--luna-fx-outer-offset-x | float | Horizontal offset (px). |
--luna-fx-outer-offset-y | float | Vertical offset (px). Positive = down. |
--luna-fx-outer-color | color | Halo color. Use var(--luna-fx-grad-tone-2) for palette-tinted glow. |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-outline-width | float | Pixel width. 0 = dormant. |
--luna-fx-outline-opacity | float | 0 = dormant (animatable). |
--luna-fx-outline-color | color | |
--luna-fx-outline-softness | float | 0 = crisp, higher = blurred edge. |
--luna-fx-outline-pattern-image | sprite | URL/resource. Tints into the outline (dashed, hearts, …). |
--luna-fx-outline-pattern-scale | float | |
--luna-fx-outline-pattern-opacity | float | |
--luna-fx-outline-pattern-tint | color | |
--luna-fx-outline-dash-count | float | 0 = solid; non-zero gives dashed segments. |
--luna-fx-outline-dash-ratio | float | Dash : gap ratio. |
--luna-fx-outline-circling-speed | float | Animated rotation speed. |
--luna-fx-outline-shine-speed | float | Travelling-shine speed. |
--luna-fx-outline-shine-size | float | Travelling-shine band size. |
--luna-fx-outline-shine-color | color | |
--luna-fx-outline-gradient | string | CSS gradient applied to the outline (e.g. rainbow ring). |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-inner-opacity | float | 0 = dormant (animatable). |
--luna-fx-inner-softness | float | Edge blur. |
--luna-fx-inner-color | color | Default semi-transparent black. |
--luna-fx-inner-width | float | Shorthand — sets all four edges. |
--luna-fx-inner-width-left | float | Per-edge override. |
--luna-fx-inner-width-top | float | Per-edge override. |
--luna-fx-inner-width-right | float | Per-edge override. |
--luna-fx-inner-width-bottom | float | Per-edge override. |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-gradient | string | CSS gradient string. References to --luna-fx-grad-tone-* resolve at draw time, so palette swaps re-tint without re-parsing. |
--luna-fx-gradient-opacity | float | |
--luna-fx-gradient-angle | float | Linear gradients only. |
--luna-fx-gradient-type | float | 0 = Linear, 1 = Radial. |
--luna-fx-gradient-reverse | float | 0 / 1. |
--luna-fx-gradient-color-bias | float | Push midpoint of two-stop gradients. |
--luna-fx-grad-tone-1/2/3 | color | The three tones a fill class references. Set by palette/color-binding classes. |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-shine-opacity | float | 0 = dormant. |
--luna-fx-shine-color | color | |
--luna-fx-shine-width | float | Band width (0–1 of element). |
--luna-fx-shine-angle | float | Degrees. |
--luna-fx-shine-cycle-time | float | Seconds per loop. |
--luna-fx-shine-sweep-angle | float | Total arc the shine traverses. |
--luna-fx-shine-mode | float | 0 = Linear, 1 = Radial. |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-flash-intensity | float | 0 = dormant (animatable). |
--luna-fx-flash-color | color | |
--luna-fx-flash-speed | float | Hz. |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-tile-image | sprite | Required to light up. |
--luna-fx-tile-opacity | float | 0 = dormant. |
--luna-fx-tile-color | color | |
--luna-fx-tile-scale | float | |
--luna-fx-tile-rotation | float | Degrees. |
--luna-fx-tile-rate | float | Animation rate. |
--luna-fx-tile-speed | float | Translation speed. |
--luna-fx-tile-coverage-start | float | Reveal start (0–1). |
--luna-fx-tile-coverage-end | float | Reveal end (0–1). |
--luna-fx-tile-coverage-start-softness | float | |
--luna-fx-tile-coverage-end-softness | float | |
--luna-fx-tile-cell-zoom-start | float | |
--luna-fx-tile-cell-zoom-end | float | |
--luna-fx-tile-full-reveal | float |
| Variable | Type | Notes |
|---|---|---|
--luna-fx-dissolve-rate | float | 0 = dormant. Animate to 1 to fully dissolve. |
--luna-fx-dissolve-image | sprite | Optional — built-in noise pattern is the fallback. |
--luna-fx-dissolve-color | color | Edge-band tint. |
--luna-fx-dissolve-width | float | Edge-band width. |
--luna-fx-dissolve-softness | float | |
--luna-fx-dissolve-scale | float | Pattern scale. |
Append -hover, -active, or -disabled to any animatable scalar to declare its target state value. The auto-attached UIEffectStateTween interpolates smoothly:
.my-card {
--luna-fx-outer-strength: 0.5;
--luna-fx-outer-strength-hover: 0.9; /* tweens to here on hover */
--luna-fx-outer-strength-active: 0.3;
--luna-fx-outer-strength-disabled: 0.2;
}Pseudo-class selectors (:hover, :active, :enabled, :disabled) work too and snap immediately. luna-fx-btn uses both: suffix vars for the tween, pseudo-class forwarders so non-tweened renderers still snap correctly.
In LunaUIManager Inspector:
true by default. Every UIView automatically calls UIEffectPanelHook.Install(view.ParentElement, "luna-fx") on attach, so all descendants carrying the marker class get wired up. No code needed.For raw UIDocument use (no UIView), or to gate which subtrees are eligible:
using CupkekGames.Luna.Effects;
void Start()
{
var root = GetComponent<UIDocument>().rootVisualElement;
UIEffectPanelHook.Install(root, UIEffectClassDriver.DefaultMarkerClass);
}The marker class must be present before the element enters the panel for auto-pickup. If you toggle the marker after attach, call UIEffectClassDriver.Attach(element) directly.
The system needs to find the LunaUIEffect Shader Graph at runtime. Create a UIEffectSettings asset (Assets > Create > CupkekGames/Luna UI/UIEffectSettings) and assign the shader. Without it, Shader.Find("Shader Graphs/LunaUIEffect") is the fallback, which only works if a material in the build references the shader.
The Showcase sample ships three Effects scenes:
Components/Effects/EffectsShowcase.unity — gallery of every preset class on every base shape. Browse this first.Components/Effects/EffectsPresets.unity — preset cards isolated for screenshot grids.Components/Effects/EffectsPlayground.unity — interactive playground. Toggle effects, scrub uniforms, see the shader uniforms update live.UIEffect_Flair.uss is the source for the multi-stop / animated-outline classes listed above — copy what you like.
luna-fx marker class hooks the element into UIEffectClassDriver. The driver builds a default stack (Outer + Outline, both dormant) and registers CustomStyleResolvedEvent to react to USS changes.--luna-fx-* variables. They never replace the stack, so every combination composes naturally.LunaEffectUSSOverrides reads the resolved --luna-fx-* values from the element's customStyle and writes them into the shader's MaterialPropertyBlock — overriding whatever the C# stack baked. USS is always the final word.--luna-fx-gradient, --luna-fx-outline-gradient) are parsed once into a retained template; pseudo-state tone shifts re-resolve var() references without re-parsing.Luna effects respect the standard UITK visibility properties — opacity, visibility, and display — including ancestor inheritance. A faded parent fades its filtered children's gradients, glows, shadows, shines, and every other decorative pass uniformly with the element's own content.
Every frame the filter pass runs, Luna walks the target up its hierarchy and folds the result into a single _LunaElementOpacity shader uniform:
resolvedStyle.opacity on each ancestor is multiplied together (cumulative).display: none or visibility: hidden collapses the value to 0.The shader multiplies its final composited output by this uniform. Decorative passes (gradient, outer glow/shadow, shine, flash, tile, etc.) don't read element content — they generate pixels directly — so without this multiply they would render at full intensity even when the element's own content is faded by UITK's pre-multiplied opacity.
The walk happens inside the filter pass's per-frame OnApplySettings callback, so:
resolvedStyle.opacity natively; Luna picks up the live value every frame and the shader fades smoothly with the element.display: none, the filter pass itself is detached (no SetPass cost). GeometryChangedEvent fires on the toggle back, re-attaches automatically.filter: blur() and filter: drop-shadow()opacity on an ancestor doesn't reach UITK-filtered subtrees through the cascade. UITK renders each element with style.filter into an isolated pass, and that pass's output is composited back without ancestor opacity propagation. This is UITK behavior, not Luna behavior — Luna's own filter pass works around it via _LunaElementOpacity, but native blur / drop-shadow use Unity's built-in filter pipeline which Luna can't reach.
Symptom: you fade a screen with opacity: 0.1 on a parent, every element fades except one — a stubborn dark rectangle from a blurred backdrop, or a leftover drop-shadow.
Fix: write inline opacity directly on the filtered element, not an ancestor.
/* ✗ Doesn't fade — UITK filter isolation breaks the cascade. */
.modal-container { opacity: 0.1; }
.modal-container .scrim-bg { filter: blur(8px); background-color: rgba(0,0,0,0.4); }
/* ✓ Fades — write opacity on the filtered element itself. */
.modal-container .scrim-bg.fading { opacity: 0.1; }// Or in C# — set the inline opacity on the filtered element:
scrimBg.style.opacity = 0.1f;Alternative: animate the filter parameter to zero instead of opacity. filter: blur(0px) produces no visible blur. This avoids the isolation entirely.
If you author Luna effects exclusively, you'll never hit this — Luna's pipeline handles ancestor opacity correctly. The gotcha applies only when you mix in UITK's native filter functions.
For dynamic cases that don't fit USS — gameplay-driven effects, pooled VFX overlays, programmatic preview tools — the C# API is unchanged:
using CupkekGames.Luna.Effects;
// Direct API
var fx = new UIEffectElement(myElement);
fx.AddEffect(new OuterEffect { Color = Color.red, Strength = 1f, Width = 12f });
fx.AddEffect(new ShineEffect { Opacity = 0.4f, Width = 0.1f });
fx.RefreshEffects();
// One-off override on a USS-driven element (pinned over the dormancy gate)
LunaEffectOverride.Apply(myElement, new OuterEffect { Strength = 1.5f });Most projects don't need this — the USS path covers static styling, hover/active states, theme swaps, and palette tinting. Reach for C# only when the effect parameters genuinely need to live in code.
--luna-fx-* resolved set is cached per element and rebuilt on CustomStyleResolvedEvent. The per-frame write path iterates only the resolved bindings, not the full vocabulary.| Symptom | Fix |
|---|---|
| No effects visible | LunaUIManager in scene? Auto Install Fx enabled? Element has the luna-fx marker class? Element has non-zero size? |
Background is washed-white / blank when using luna-fx-shadow-* | The element has overflow: hidden. Either drop it, or pair with a fill class (luna-fx-clay / matte / bar) and a color binding so the gradient paints the surface. |
| Glow renders black | --luna-fx-grad-tone-2 isn't set. Apply a palette/color-binding class (luna-fx-yellow, etc.) or set --luna-fx-outer-color directly. |
| Shader-not-found at runtime | Create the UIEffectSettings asset and assign the LunaUIEffect Shader Graph (see Setup). |
| Marker class added at runtime not picked up | Call UIEffectClassDriver.Attach(element) manually — auto-pickup only fires for elements that carry the marker on panel-attach. |
Parent opacity fades the screen but one filtered element stays visible | Element has UITK's native filter: blur() / filter: drop-shadow(). UITK isolates filter passes from the opacity cascade — write inline opacity directly on the filtered element, or animate the filter parameter to zero instead. See Opacity & visibility. |
Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction