On the left is a tonal shadow, on the right is monochrome. I’m going to stick to the first one.

To implement one, we need a couple of small additions.

Addition #1: Color mixer.

I do prefer excessive use of extensions for adding new functionality to existing entities when using Dart. Even for the simplest tasks which already covered by the framework. So here you have it.

It could be used as Colors.blue.mix(Colors.red, .5) . If you don’t like how it looks, you could stick to using Color.lerp directly.

Addition #2: Theme awareness

Let’s make use of awesome Theme support in Flutter and make our NeumorphicContainer support to be drawn on a standard background s.

For that, let’s define the background color behavior, in NeumorphicApp s ThemeData .

It’s not ugly (but it’s not a screenshot either)

And let’s add color property to NeumorphicContainer . Which would fetch its value from ThemeData but support overrides where needed. Always leave your users a way to override defaults. Especially if you’re the future user of the code you’re writing.

The only thing left is to mix this Colors.white and Colors.black to this color and enjoy tonal shadows.

You may see that I’ve used different values for white and black here. This disproportion would shift depending on luminosity of the color . dart:ui have a handy computeLuminance which returns a unit luminosity (value between 0 and 1) for a given color and could be used to programmatically adjust the dominant shadow.

Still meh.

Let’s move on. Next step is to apply our Spherical drill bit to the top of this surface.

Neumorphic design allows a couple of options here: convex and concave. That difference determines how lighting will be applied to our surface.

At least some of the screenshots in the article looks good.

Let’s start with a latter one and a grain of salt. Flutter doesn’t support inset shadows. At least for now. So we’ll have to go with Gradients, for now.

Which looks as following.

Netscape navigator vibes, anyone?

Alright. Let’s check what we got.

Direct light shadow. Present.

Counter light reflection. Present.

Concave surface shape. Present.

But it still looks ugly!

Widely known design secret #2. When something looks ugly but should look nice, just add space.

Space could fix a bad design and turn good design to be a great one. Also, space is what most of the universe consists of, taking into account that the radius of an atom is more than 10,000 times more than the radius of its nucleus. That’s right, only 0.0001% of the universe’s matter is actually something solid. So don’t try to go against that universal law of nature. Just add some space.

With that and some text color tweaks, our surface looks light years better immediately.

Space

We can now also make gradient a little bit more complex, to adapt for this pill shape.

Ok, now it’s something I wouldn’t be too shy to show to someone!

The final push would be to make our container respond to touch gestures. To do so, let’s sprinkle some Listener on top of the Container . And convert NeumorphicContainer to be a StatefulWidget , as it’ll now have isPressed state.

To the users of VS Code, there’s a very convenient helper. Just place text caret into the widget class name and hit Cmd/Ctrl + . , or Cmd/Ctrl + Shift + R ` or right click the class name and choose “Refactors”. That’ll call a context menu with “Convert to StatefulWidget” option. Very convenient!

Saved me hours. Literally.

After that, we can wrap Container in NeumorphicContainer with Listener , using another automatic refactor, wrap with widget .

A productive developer is a happy developer. So know your tools to be happy!

And pressing our container now… Does nothing! We need to use this _isPressed first.

We could do that by conditionally adding or removing boxShadow depending on if _isPresset is set.

And voila!

I mean, “Voila, our ugly interaction is ready”. Let’s make it nicer.

Making things nicer #1.

After button is pressed, it’s surface is squished, so some interactivity is expected. Let’s change our gradient look depending on _isPressed

You can tweak these color variation to your liking as per effect you’re trying to achieve.

Making things nicer #2.

It’s way too snappy. Not that users don’t love snappy interfaces, but this one might be too much. With Flutter, it’s extremely easy to fix this. We literally need just one new line of code. Just swap Container with AnimatedContainer and add a required duration param. 150ms would feel pretty snappy still, but you can tweak that as per your preference. Just don’t make it less than 0, otherwise, your Dart VM instance will explode.

And we’re done, a simple, yet quite alive sample of Neumorphic UI is ready!

A complete code example is available in this gist.