Library / SDK
glorylab/wave avatar
glorylab/wave

WAVE: animated wave backgrounds as a Flutter widget with a code generator

A Flutter package for displaying waves.

1,124 stars95 forksDartMIT

At a glance

What is it?
WAVE is a Flutter package that paints layered animated wave backgrounds, offering three configuration modes for the palette and a web demo that turns slider adjustments into ready to paste widget code. It suits Flutter apps that want a hero background quickly, and the demo's generated code is the fastest way to get a configuration you like.
Who is it for?
WAVE fits a Flutter project that wants a layered wave background behind a header or an onboarding screen, because the widget takes a config and a size and nothing else, and because the web generator produces the exact code for a configuration you have dialled in rather than making you translate from documentation.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 67 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One widget, three ways to describe the palette

WAVE is a Flutter package for displaying animated wave backgrounds, and the whole public surface is a single widget called `WaveWidget` plus a set of config classes.

The widget is placed like any other widget that paints. The minimal example is a `StatelessWidget` returning a `WaveWidget` with a config and a size:

dart
WaveWidget(
  config: SingleConfig(
    color: Color(0xFF00BBF9),
    layers: 3,
  ),
  size: Size(double.infinity, double.infinity),
)

The documentation is explicit about one requirement, and it is the kind of thing that costs an afternoon to debug if missed. Give it a bounded parent, such as `SizedBox.expand`, `Positioned.fill`, or a fixed-height container. A widget that paints into infinity inside an unbounded parent has nowhere to paint.

The three config modes correspond to three levels of control. `SingleConfig` creates layered waves from one base color and is the quickest to reach for. `RandomConfig` creates generated colors for each layer, and it takes a `seed` when deterministic output is useful for tests, demos or copyable examples, with the instruction to omit `seed` only when you really want a new generated palette for each config construction. `CustomConfig` is the most explicit mode, for when you want full control over each layer's color or gradient, duration and height.

The naming is honest about which one to reach for. Single is one color, Random is generated, Custom is exact. There is no fourth mode, and no mode that mixes generated and exact across layers, so the choice is a decision about how much of the palette you are willing to think about.

Per-layer durations and heights are the real controls

The parameters that make a wave configuration look right are not the ones on `WaveWidget` itself but the per-layer lists inside a config.

`CustomConfig` takes a list of gradients, a list of durations and a list of height percentages:

dart
WaveWidget(
  config: CustomConfig(
    gradients: const [
      [Color(0xFF00BBF9), Color(0xFF9B5DE5)],
      [Color(0xFFFEE440), Color(0xFFF15BB5)],
    ],
    durations: const [5000, 4000],
    heightPercentages: const [0.65, 0.66],
  ),
  size: const Size(double.infinity, double.infinity),
)

The durations differ between the two layers, 5000 and 4000 milliseconds, and that difference is what stops the layers from moving in lockstep. Equal durations would produce a rigid stack. The height percentages are close together, 0.65 and 0.66, so the layers sit at almost the same vertical position with a slight offset, which is the effect that reads as a rolling wave rather than as separate ribbons.

The parameters on `WaveWidget` are the global ones. `waveAmplitude` defaults to 20.0 and is the base vertical movement of the wave path. `wavePhase` defaults to 10.0 and sets the initial phase offset for the animation. `waveFrequency` defaults to 1.6 and is the horizontal frequency of the wave path. `duration` defaults to 6000 and is the total runtime in milliseconds when `isLoop` is false, and `isLoop` defaults to true, so by default the animation repeats indefinitely.

There are also two background parameters, `backgroundColor` and `backgroundImage`, both defaulting to null, which sit behind the wave stack.

A useful detail: `wavePhase` is described as the initial phase offset. That means two widgets in the same build with the same config can be desynchronised by changing that one number, which is a cheap way to stop repeated sections of a page from animating in lockstep.

The web generator is the fastest path to a good looking result

The demo is not a screenshot gallery, it is a tool. The web demo at wave.glorylab.xyz is an interactive generator, and the workflow it supports is: adjust the config mode, palette, layer count, height, amplitude, speed and loop behavior, then copy the generated `WaveWidget` code into your app.

That is a much better workflow than reading a parameter table and guessing. The parameters interact, amplitude and frequency especially, and the only reliable way to know that a particular combination looks right is to see it move. The demo shows a live preview with performance metrics, and a generated code dialog entry so you can take the result with you.

The list of controls also tells you what the package considers the interesting knobs, and it matches the parameter list closely: mode, palette, layer count, height, amplitude, speed, loop behavior. Everything the widget exposes is reachable from the generator, which is a good sign that the public API is complete.

The demo is built from the `example/` directory and deployed with Cloudflare Workers Static Assets, with the GitHub Actions and migration setup documented in `docs/cloudflare-workers.md`. The repository top level carries a `wrangler.jsonc`, which is the Cloudflare Workers configuration file, and the presence of a `.fvmrc` file shows the project pins its Flutter SDK version.

So the workflow for a user is: open the demo, dial in something you like, copy the code, paste it into your app. The documentation is mostly there to explain the parts the generator cannot show you, which is the parameter defaults and the error conditions.

The three mistakes the configuration will throw at you

The documentation has a Common Errors section, and the three rules are all about list consistency. They are worth knowing before you run into them, because each produces an assertion rather than a graceful degradation.

The first is that `CustomConfig` requires exactly one of `colors` or `gradients`. Supplying both is the mistake:

dart
CustomConfig(
  colors: const [Color(0xFF00BBF9)],
  gradients: const [
    [Color(0xFF00BBF9), Color(0xFF9B5DE5)],
  ],
  durations: const [5000],
  heightPercentages: const [0.65],
)

The second is that all per-layer lists must have the same length. A two colour list with a one element duration list is the mistake:

dart
CustomConfig(
  colors: const [Color(0xFF00BBF9), Color(0xFF9B5DE5)],
  durations: const [5000],
  heightPercentages: const [0.65, 0.66],
)

The third is a range rule rather than a list rule. `heightPercentages` and `opacityPercentages` values must be between 0 and 1, and every `duration` must be positive.

The reason these rules exist is structural, and it is worth understanding rather than just memorising. The config describes a stack of layers, and the renderer has to zip the per-layer lists together to build each layer. If the lists disagree in length there is no correct answer to which layer gets which duration, so the package refuses. If a height percentage is outside zero to one the layer would be inverted or degenerate, and a non-positive duration would produce a division by zero or an infinite animation.

So the length rule is not arbitrary strictness, it is a guarantee that the zipped layers are well defined. The practical habit that avoids all three is to build the lists together, from a single layer count, rather than writing them independently.

What this package is and is not for

The honest scope is narrow, and the value depends on whether that narrow thing is what you need.

What WAVE is: a self-contained animated background widget, with a design that is already decided. The layers, the gradient blending, the wave shape and the animation are all part of the package. You choose a palette and a rhythm and you get a coherent result.

What it is not: a general wave or ripple effect library. The parameters are fixed and documented, and there is no way to supply your own painter, your own wave function or your own shape. If you need a wave that responds to touch, to a scroll offset or to a value in your state, this widget does not offer a hook for it, because every animation input is a plain property set at construction time.

That boundary matters for a specific common case. A wave background that parallaxes as the user scrolls is a well known visual, and it needs the wave phase to be driven by the scroll position. With this package, `wavePhase` is an initial offset, not a live input, so that effect is not something you get by configuration. You would be rebuilding the widget rather than configuring it.

The alternative approaches are worth naming. A `CustomPainter` written by hand gives you complete control, and it is what you would reach for if the wave needs to react to anything. A video or an animated asset gives a richer result with none of the code, at the cost of a file in your bundle and the memory to decode it. And a shader-based effect gives the most striking result if your target platforms support it, with a much higher cost in complexity.

The case for WAVE over the hand written painter is speed and consistency. A painter takes time to write, to tune and to get right on every screen size, and the result is usually not better. The case against it is precisely the reactivity gap.

On maintenance, the package is MIT licensed and published on pub.dev as `wave`, the repository is not archived, and the last push was on 2026-08-17. The release history is tight, with 0.2.3, 0.2.4 and 0.2.5 all tagged on 2026-07-08 within a few hours of each other, which looks like a single release session across three patch versions. The install snippet in the documentation still reads `wave: ^0.2.4`, so pinning to the latest patch needs a small edit.

Editorial conclusion

WAVE fits a Flutter project that wants a layered wave background behind a header or an onboarding screen, because the widget takes a config and a size and nothing else, and because the web generator produces the exact code for a configuration you have dialled in rather than making you translate from documentation. Skip it if you need the wave to react to scrolling, to user input or to a state change, since the animation parameters are static properties of the widget rather than reactive inputs, and skip it if the visual weight of a continuously animating background does not suit the design. Before you ship it, check the version you pin, because the install snippet in the documentation still shows `wave: ^0.2.4` while the released version is 0.2.5, and the last push was on 2026-08-17.

Frequently asked questions

How do I add a wave background to a Flutter app?

Add `wave: ^0.2.4` to your dependencies, import `package:wave/wave.dart`, and place a `WaveWidget` with a config such as `SingleConfig(color: Color(0xFF00BBF9), layers: 3)` and a size, giving it a bounded parent such as `SizedBox.expand`.

What is the difference between SingleConfig, RandomConfig and CustomConfig?

`SingleConfig` builds layered waves from one base color, `RandomConfig` generates a palette per layer and accepts a `seed` for deterministic output, and `CustomConfig` gives exact control over per-layer colors or gradients, durations and heights.

Why does my CustomConfig throw an error?

Two rules cause it: `CustomConfig` requires exactly one of `colors` or `gradients`, and all per-layer lists must have the same length. Height and opacity percentages must be between 0 and 1, and every duration must be positive.

How do I get a wave configuration that looks right?

The web demo at wave.glorylab.xyz is an interactive generator, with controls for the config mode, palette, layer count, height, amplitude, speed and loop behavior, and it emits the `WaveWidget` code to copy into your app.

Can the wave animation react to scrolling or touch?

Not through configuration. The animation inputs such as `waveAmplitude`, `wavePhase` and `waveFrequency` are properties of the widget set at construction rather than reactive values, so a scroll-driven or interactive wave would need a custom painter instead.

Official sources

  1. glorylab/wave on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/glorylab-wave.svg)](https://hysenlabs.com/projects/glorylab-wave)

Was this useful?