---
title: "BRAND Duolingo 2026 · Recipe: juicy-button"
url: https://memocore.ai/memos/6abe2d7957d33af4d524764d
project: "Brand Styles"
tags: "brand-duolingo"
updated_at: "2026-10-01T10:12:22.160Z"
---

# BRAND Duolingo 2026 · Recipe: juicy-button

HEADER
brand: Duolingo; design_date: 2026-10; kind: component; used_in: every page (Page: home S1, S2, S5, S6, S7; Page: login)
evidence: measured from the button CSS module (4779-c36ad395.css: _1VYyp solid, _2V6ug stroke, _1ursp base) and computed styles on https://www.duolingo.com/ 2026-10-01 unless marked

LOOK
A flat, rounded rectangle that looks like a thick physical key: its colored face sits on a solid darker "lip" 4px tall, with no blur. On hover the whole button just gets a little brighter. When pressed, the face drops 4px onto its lip and the lip disappears, so the button feels pushed into the page; on release it pops back. White secondary buttons have a 2px light-grey outline with a thicker 4px grey bottom edge and drop 2px.

STRUCTURE
button or a (inline-flex, centered, position relative, z-index context) -> ::before layer (absolute, inset 0, z-index -1) that paints the face color and carries the lip as box-shadow -> label span (uppercase, nowrap, ellipsis). The element itself has a transparent background and a transparent bottom border as tall as the lip, so the clickable box includes the lip.

KEY PARAMETERS
solid (primary): height 50px (44px in the topbar, 48px "juicy" variants in the app); padding 0 16px (0 20px in the topbar); border-width 0 0 4px, border-color transparent; border-radius 12px (16px in the login sheet; the app default is 16px on :root); ::before background = face color, border-radius same, box-shadow 0 4px 0 = lip color (via color: lip; box-shadow uses currentColor)
label: duolingo-sans 700 15px, line-height 1.2, letter-spacing 0.8px, uppercase, color white
color pairs (face / lip / label): owl #58cc02 / tree-frog #58a700 / #ffffff (GET STARTED); macaw #1cb0f6 / whale #1899d6 / #ffffff (LOG IN, design-system default); white #ffffff / rgba(255,255,255,0.5) / eclipse #000437 (on the navy Super band); bee #ffc800 / camel #e7a601; cardinal #ff4b4b / fire-ant #ea2b2b; humpback #2b70c9 / narwhal #1453a3
stroke (secondary): height 50px; border 2px 2px 4px transparent; ::before: inset -2px (left, right, top, bottom), background #ffffff, border 2px solid #e5e5e5, box-shadow 0 2px 0 #e5e5e5 (makes the 4px bottom), radius 12px; label 15px 700 uppercase, color #1cb0f6 (or provider color: Google #4285f4, Facebook #3b5998, Apple #4b4b4b)
disabled: face #e5e5e5, label #afafaf, no lip offset change, no hover
sizes: small 32px tall, 14px label, radius 8px, padding 0 7-8px; pill variant 26px tall, radius 16px, 2px lip (app only)

MOTION
hover (only @media (hover:hover)): filter brightness(1.1) on solid buttons, brightness(0.9) on white stroke buttons, opacity(0.8) on transparent buttons over color; transition filter 200ms ease
press (:active, and a "fake-active" class for scripted presses): transform translateY(4px) translateZ(0) for solid (2px for stroke); ::before box-shadow none; label color may switch to a "color-active" value (same by default)
release: back to rest
the transform and box-shadow use transition var(--web-ui_button-transition-lip), which is unset on the landing (instant press) and 100ms linear inside lesson screens
no scale, no color change, no ripple

STATES
focus-visible: none (outline: none on all buttons); a rebuild should add a visible ring for accessibility but it is not part of the look
touch: the press offset shows on touch; hover brightness is not applied on touch devices
submitting: the button stays in the pressed position and its label is replaced by loading dots (inf)
reduced motion: same (the press is instant anyway)

HOW TO BUILD
A button with position relative, transparent background and a transparent bottom border equal to the lip; a ::before pseudo-element behind the label that holds the face color, the radius and a hard box-shadow 0 <lip> 0 <lip color>. :active translates the button down by the lip and removes the shadow. Hover uses filter: brightness so one rule works for every color.

PITFALLS
the lip is a hard shadow (0 blur, 0 spread) in a darker shade of the SAME hue, never grey or black on colored buttons; the total height must include the lip (50px = 46px face + 4px lip); the label is uppercase with 0.8px tracking and bold, never sentence case; do not animate the press slowly (it is instant or 100ms linear); do not change background color on hover, only brightness; corners are 12px, not pill

SCREENSHOTS
- file: duolingo_2026_overview_d1440_states.png
  shows: GET STARTED default (#58cc02 face, #58a700 lip) / hover (brightness 1.1) / active (moved 4px down, no lip); I ALREADY HAVE AN ACCOUNT default / hover (brightness 0.9) / active (moved 2px down, bottom edge gone)
