All articles
RiveDuolingoUI AnimationState MachineData BindingButtonsNo Code

How to Make Duolingo's 3D Button in Rive (No Code)

|7 min read
How to Make Duolingo's 3D Button in Rive (No Code)

You know this button. The chunky green one you've pressed a thousand times in Duolingo.

Here's what most people don't know: Duolingo builds its animations in Rive. The characters, the reactions, the lip sync, all of it runs on the exact tool we're about to use. So this isn't a lookalike exercise in some random app. It's the real production pipeline.

In this tutorial we'll build that button from scratch: the 3D depth effect, the real press feel, and the best part, by the end you'll be able to swap the button's color and text in one click without touching the animation itself. No code anywhere.

Why the button feels 3D

The whole effect is one trick: the button is two shapes, and the top one travels 4 pixels.

That's it. A face layer, a base layer sitting 4 pixels lower, and a press that moves the face down until it lands flush on the base. Your brain reads the visible edge as depth, and the travel as a physical click. Duolingo wraps it in good timing and derived colors, and that's exactly what we'll build.

Step 1 - Two shapes

Create a new file with a 300x100 Artboard as the button's container.

  • Draw a Rounded Rectangle, 240x40, with a 12 corner radius
  • Fill it with a bright green, close to Duolingo's but pick your own shade
  • Center it on the Artboard
  • Duplicate the rectangle and move the copy 4 pixels down. That's the base

Right now both shapes are the same color, so there's no depth yet. You could just pick a darker green for the base, but don't. There's a better way, and it's the idea that carries this whole tutorial.

Step 2 - Darken, don't recolor

Select the bottom rectangle and add a second Fill on top of its green: solid black, at 20% opacity.

Now the base is a darker version of whatever color sits under it. Not a second green you picked by hand, a shade that is derived from the first one. This matters because in a minute the button's color will come from data, and any color that arrives will automatically get a matching darker base. One color input drives the entire button.

Step 3 - The label

Add a Text element with the word CONTINUE in caps.

Duolingo's typeface is custom, so we'll fake the feel with a free Google font:

  • Nunito Black gets you the rounded, chunky look
  • Squeeze it to 85% on the X axis only, Duolingo's font is slightly condensed
  • Add a bit of letter spacing
  • Size it to almost the full width of the button and center it

Play with it until it reads right to you. The recognition comes from the styling (caps, heavy weight, spacing), not from the exact font.

Step 4 - Group it like it moves

When the button is pressed, the face and the label travel together and the base stays put. So the hierarchy should say exactly that:

  • Button-M - the master group
  • Face - a group holding Face Shape (top rectangle) and Label (the text)
  • Base - the bottom rectangle
Best Value

Rive Masterclass + Rive Scripting

Master Rive for real products.

Get Both for $197

Both courses for $197. Save $50.

Rive ScriptingRive Masterclass

One group to move, one shape that never moves. Meaningful names now will pay off when we wire things up.

Step 5 - One color, one label, from data

Open the Data panel and rename the default View Model to Button View Model. Add two properties:

  • buttonColor (Color), default green
  • buttonLabel (String), default CONTINUE

Now connect them with Data Binding: bind the green Fill of Face Shape and the green Fill of Base to buttonColor, and bind the text run of Label to buttonLabel.

Run it and change the values. The whole button re-skins itself, face, base shade, and text, from two properties. This is why we split the darkening into its own black layer: both greens point at the same source, and the depth comes along for free in pink, red, or anything else you type in.

Step 6 - Two states, and the animation lives between them

Here's the approach: we don't animate a press. We define two poses and let the transition do the motion.

  • Rename the default timeline to Default, and key the Face group at its resting Y
  • Duplicate it, name it Pressed, and move Face down until it sits flush on Base
  • In Pressed, also darken the face: add another black Fill on Face Shape, key its opacity at 0% in Default and 20% in Pressed

(Prefer a brighter press instead? Make that extra Fill white instead of black. Same principle: the press effect is derived from the color, not painted over it.)

In the State Machine, add a Boolean input isPressed, then two transitions: Default to Pressed when isPressed is true, and back when it's false. Set the transition duration to 100ms with cubic easing, and the snap becomes a smooth, natural click.

Step 7 - Make it listen

The button still doesn't respond to a real press, so let's add Listeners:

  • Pointer Down on Face Shape sets isPressed to true
  • Pointer Up sets it back to false

Why not a Click listener? Because a click fires once. With down and up as separate events, the button stays pressed as long as your finger holds it, exactly like the real thing. Press, hold, release. That's the feel.

Run the State Machine and press the button. Face drops, darkens, springs back on release. And the color and label are still live data, change them mid-run and the press keeps working, untouched.

Where this goes next

This button is part one of a series. Coming next: the success moment, a particle burst and shine clipped to the button's shape, and then making the whole thing a responsive component you can drop into any layout at any size.

Everything here, the State Machine, Data Binding, Listeners, easing, goes much deeper than one button. If you want the full picture, that's exactly what the course below covers.

Get more Rive tips

Weekly tutorials, new lessons, and Rive community highlights — no spam.

Related articles