Flutter package
flutter_shaders_ui
GPU-accelerated GLSL shader effects for Flutter UI — ten drop-in widgets, zero dependencies.
API reference
Every public widget and core class with constructor parameters, types and defaults: the effects, GlowOrb, ShaderEffectWidget, ShaderPainter and ShaderCache.
source: README.mdAPI
Library exports
A single import — package:flutter_shaders_ui/flutter_shaders_ui.dart — brings in the whole public surface: three core building blocks, one callback typedef, and ten drop-in effect widgets. Nothing else is exported; the shaders themselves ship as bundled .frag assets.
| Symbol | Kind | Summary |
|---|---|---|
| ShaderEffectWidget | Widget | Base widget — loads a .frag asset, drives the time animation, sets standard uniforms. |
| ShaderUniformSetter | Typedef | The per-frame callback that sets custom shader uniforms. |
| ShaderCache | Class (static) | Global cache of compiled FragmentProgram instances, keyed by asset path. |
| ShaderPainter | CustomPainter | Reusable painter that draws a rect filled with a FragmentShader. |
| AuroraEffect | Widget | Animated aurora / northern-lights overlay. |
| FireEffect | Widget | Flickering flames rising from the bottom. |
| GlassEffect | Widget | Frosted-glass / glassmorphism overlay. |
| GlowOrb | Widget | Positionable glowing orb — static, bouncing or draggable. |
| PulseEffect | Widget | Breathing radial glow with expanding pulse rings. |
| RippleEffect | Widget | Tap-triggered expanding ripple overlay. |
| ShimmerEffect | Widget | Sweeping shimmer highlight for placeholders. |
| SnowEffect | Widget | Falling snow particles with parallax depth. |
| WaterEffect | Widget | Animated water surface with caustics and foam. |
| WaveBackground | Widget | Animated wave gradient background. |
API
Effect widget signatures
Every effect is a plain widget you drop into your tree. Their constructor signatures are listed below for quick reference. For the full parameter tables — ranges, defaults and per-parameter notes — plus a working example for each, see the Effects reference.
const WaveBackground({
super.key,
this.child,
this.color1 = const Color(0xFF1A237E),
this.color2 = const Color(0xFF00BCD4),
this.amplitude = 0.3,
this.frequency = 2.0,
this.speed = 1.0,
this.enabled = true,
})const WaterEffect({
super.key,
this.child,
this.color1 = const Color(0xFF26C6DA),
this.color2 = const Color(0xFF0D47A1),
this.speed = 1.0,
this.depth = 0.5,
this.waveIntensity = 0.5,
this.causticIntensity = 0.6,
this.foamAmount = 0.0,
this.enabled = true,
})const AuroraEffect({
super.key,
this.child,
this.color1 = const Color(0xFF00E676),
this.color2 = const Color(0xFFAA00FF),
this.intensity = 0.6,
this.speed = 1.0,
this.enabled = true,
})const FireEffect({
super.key,
this.child,
this.intensity = 0.6,
this.speed = 1.0,
this.color1 = const Color(0xFFFFEB3B),
this.color2 = const Color(0xFFFF5722),
this.enabled = true,
})const GlassEffect({
super.key,
this.child,
this.blurAmount = 0.5,
this.frost = 0.4,
this.opacity = 0.3,
this.tint = const Color(0xFFFFFFFF),
this.enabled = true,
})const PulseEffect({
super.key,
this.child,
this.color = const Color(0xFF2196F3),
this.speed = 1.0,
this.intensity = 0.5,
this.enabled = true,
})const ShimmerEffect({
super.key,
this.child,
this.color = const Color(0x40FFFFFF),
this.angle = 0.5,
this.speed = 1.0,
this.width = 0.3,
this.enabled = true,
})const SnowEffect({
super.key,
this.child,
this.density = 0.5,
this.speed = 1.0,
this.size = 0.5,
this.enabled = true,
})const RippleEffect({
super.key,
required this.child,
this.color = Colors.white,
this.duration = _defaultRippleDuration,
this.intensity = _defaultRippleIntensity,
this.onTap,
})Resolved defaults: duration = Duration(milliseconds: 800), intensity = 1.0. RippleEffect is the only effect without an enabled flag — the shader runs only while a tap animation is in flight.
const GlowOrb({
super.key,
this.position = const Offset(0.5, 0.5),
this.color = Colors.cyan,
this.radius = _defaultRadius,
this.glowIntensity = _defaultGlowIntensity,
this.pulseSpeed = _defaultPulseSpeed,
this.enabled = true,
this.child,
})
const GlowOrb.bouncing({
super.key,
this.color = Colors.purple,
this.radius = _defaultRadius,
this.glowIntensity = _defaultGlowIntensity,
this.pulseSpeed = _defaultPulseSpeed,
this.enabled = true,
this.child,
double speed = _defaultBounceSpeed,
})
const GlowOrb.draggable({
super.key,
this.position = const Offset(0.5, 0.5),
this.color = Colors.orange,
this.radius = _defaultRadius,
this.glowIntensity = _defaultGlowIntensity,
this.pulseSpeed = _defaultPulseSpeed,
this.enabled = true,
this.child,
})Resolved defaults: _defaultRadius = 0.15, _defaultGlowIntensity = 1.0, _defaultPulseSpeed = 2.0, _defaultBounceSpeed = 0.3. The default color differs per mode: Colors.cyan (static), Colors.purple (bouncing), Colors.orange (draggable).
Core API
ShaderEffectWidget
The base widget behind every effect. It loads a .frag asset through ShaderCache, runs a Ticker for the time animation, wraps the paint in a RepaintBoundary, and feeds the shader its standard uniforms. Build your own effects on top of it — see Custom shaders.
const ShaderEffectWidget({
super.key,
required this.assetPath,
this.child,
this.uniformSetter,
this.enabled = true,
this.showAsOverlay = false,
this.timeScale = 1.0,
})| Parameter | Type | Req. | Default | Description |
|---|---|---|---|---|
| assetPath | String | Yes | — | Path to the .frag shader asset. |
| child | Widget? | No | null | Optional child. Rendered behind or under the shader effect. |
| uniformSetter | ShaderUniformSetter? | No | null | Callback to set custom uniforms beyond the standard ones. |
| enabled | bool | No | true | Whether the shader is active. When false, only child renders. |
| showAsOverlay | bool | No | false | If true, shader renders on top of child; if false (default), behind it. |
| timeScale | double | No | 1.0 | Multiplier applied to the elapsed time fed into the uTime uniform. 0.5 = half speed, 0.0 freezes, negative runs in reverse. |
Standard uniforms are set automatically at indices 0-2. Anything your uniformSetter adds starts at index 3.
| Index | GLSL uniform | Description |
|---|---|---|
| 0-1 | vec2 uResolution | Widget width & height, in logical pixels. |
| 2 | float uTime | Elapsed seconds. |
ShaderEffectWidget(
assetPath: 'packages/flutter_shaders_ui/shaders/snow.frag',
child: Text('Hello'),
)Core API
ShaderCache
A global, static cache for compiled ui.FragmentProgram instances, keyed by asset path — this is what makes "compiled once" true across your whole app. It has a private constructor (ShaderCache._()), so you never instantiate it; you call its static methods. Concurrent loads of the same path are deduplicated: a second load that arrives mid-compile awaits the same in-flight future rather than compiling again. It is thread-safe for single-isolate Flutter usage.
static Future<ui.FragmentProgram> load(String assetPath)
static void evict(String assetPath)
static void clearAll()| Method | Returns | Description |
|---|---|---|
| load(String assetPath) | Future<ui.FragmentProgram> | Loads a program from the asset path, using the cache. If already loaded, returns the cached instance. If currently loading, awaits the same future (deduplication). |
| evict(String assetPath) | void | Evicts a specific shader from the cache. |
| clearAll() | void | Clears all cached (and pending) shaders. |
final program = await ShaderCache.load('shaders/snow.frag');
final shader = program.fragmentShader();Core API
ShaderPainter
A bare CustomPainter that fills its canvas rect with a ui.FragmentShader. It does no loading and no animation of its own — the contract is that you set the shader's uniforms before it paints. Pass a Listenable to repaint (for example an AnimationController or a ValueNotifier) to drive repaints; shouldRepaint always returns true. Reach for it when you want a shader on a canvas without the full ShaderEffectWidget lifecycle.
ShaderPainter({
required this.shader,
super.repaint,
})| Parameter | Type | Req. | Default | Description |
|---|---|---|---|---|
| shader | ui.FragmentShader | Yes | — | The fragment shader to render. Its uniforms must already be set. |
| repaint | Listenable? | No | null | Inherited from CustomPainter via super.repaint. When provided, the painter repaints on each tick. |
Types
Support types
The package exports one support type, the ShaderUniformSetter typedef used by ShaderEffectWidget.uniformSetter. There are no public enums.
typedef ShaderUniformSetter = int Function(
ui.FragmentShader shader,
Size size,
double time,
int index,
);The callback runs every frame. It receives the live shader, the current widget size (logical pixels), the elapsed time in seconds, and the starting uniform index (which is 3, right after the standard uniforms). Set your floats with shader.setFloat(...) starting at that index, then return the next available index so the framework knows where your uniforms ended. Colors are passed as three separate floats — color.r, color.g, color.b.
Behavior
Errors & fallback
Every effect degrades to its child, never to a crash. Three cases are worth knowing.
- Disabled (
enabled: false).ShaderEffectWidget.buildreturnschild ?? const SizedBox.shrink()and the animationTickeris stopped, so there is zero GPU cost. Every effect widget forwards its ownenabledflag down to this guard — wire it to a reduced-motion setting for accessibility. - While loading. Until the
FragmentProgramfinishes loading the widget renders only its child (the same fallback), and theTickerstarts only once the program has loaded andenabledistrue. A missing or invalid.fragasset leaves the programnull, so the effect simply never appears and your child keeps rendering — the layout is never torn down. RippleEffectidle cost. It has noenabledparameter; its internal shader is active only while a tap animation runs and switches off when the animation completes, so between taps it too renders only the child.
Zero cost when disabled
The disabled path is the intended way to pause effects — off-screen, on low-end devices, or under reduced motion. A disabled widget draws nothing on the GPU and adds no shader work to the frame.
Let's make something together.
If you have any questions about a new project or other inquiries, feel free to contact us. We will get back to you as soon as possible.
