EmpireUI
Get Pro
← Blog8 min read#y2k#button#css

Y2K Button Design in CSS: Shiny, Beveled, Bubbly and Very 2000

Recreate the shiny, beveled, bubbly buttons of the early 2000s web with pure CSS. Y2K button design tutorial with code examples, gradients, and real retro vibes.

Shiny beveled Y2K style glossy button design on dark background

Why Y2K Buttons Are Having a Moment Again

If you were building websites in 2001 or 2002, you know exactly what we're talking about — those shiny, almost liquid-looking buttons with the hard bevel edges, the highlight strip running across the top third, and the drop shadow that looked like it belonged on a CD-ROM interface. They were everywhere. Winamp skins, forum software, early Macromedia Flash sites, Windows XP's default UI. And now, in 2026, they're back.

Honestly, the revival makes total sense. Flat design has been dominant for so long that anything with visible depth feels radical. Designers who grew up using those early web interfaces are now the ones making aesthetic decisions, and nostalgia is a hell of a drug. That said, the goal isn't to clone 2002 — it's to take the defining visual grammar of that era and translate it into something that works with modern CSS and component libraries.

The defining characteristics of a Y2K button are pretty specific: a glossy highlight (usually a white or light-colored semi-transparent ellipse in the top half), a hard bevel created with multiple box shadows or border colors, a saturated base color — think electric blue, hot pink, lime green — and often a slight 3D press state on :active. Get those four things right and you're there.

Worth noting: this aesthetic lives right next to vaporwave and cyberpunk in terms of retro digital vibes, but Y2K is distinctly its own thing. Vaporwave is dreamy and pastel; cyberpunk is dark and neon. Y2K is plastic, shiny, and almost aggressively cheerful. Think Tamagotchi, not Blade Runner.

The CSS Recipe for a Classic Glossy Button

Let's build the core effect from scratch. The glossy highlight is the signature move, and you get it with a pseudo-element — a ::before with a radial or linear gradient set to white/transparent, positioned over the top half of the button and clipped with border-radius and overflow: hidden on the parent. The bevel comes from layered box-shadow values.

.y2k-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 28px;
  font-family: 'Tahoma', 'Arial', sans-serif;
  font-size: 13px;
  font-weight: bold;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.5);

  /* Saturated base — classic Y2K electric blue */
  background: linear-gradient(180deg, #3a8fff 0%, #0055cc 100%);
  border-radius: 6px;
  border: 1px solid #003fa0;
  overflow: hidden;
  cursor: pointer;

  /* The bevel: highlight top, shadow bottom, outer glow */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),   /* top highlight edge */
    inset 0 -1px 0 rgba(0, 0, 0, 0.25),        /* bottom shadow edge */
    0 2px 4px rgba(0, 0, 0, 0.4),              /* drop shadow */
    0 0 0 1px #1a6fff;                         /* outer glow ring */
}

/* The glossy sheen — white ellipse in the top half */
.y2k-btn::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 55%;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.6) 0%,
    rgba(255, 255, 255, 0.05) 100%
  );
  border-radius: 5px 5px 60% 60% / 5px 5px 30px 30px;
  pointer-events: none;
}

.y2k-btn:hover {
  background: linear-gradient(180deg, #5aabff 0%, #0066ee 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    inset 0 -1px 0 rgba(0, 0, 0, 0.2),
    0 3px 8px rgba(0, 0, 0, 0.45),
    0 0 0 1px #3a8fff;
}

.y2k-btn:active {
  background: linear-gradient(180deg, #0055cc 0%, #3a8fff 100%);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.35),
    0 1px 1px rgba(0, 0, 0, 0.2);
  transform: translateY(1px);
}

A few things to pay attention to. The ::before pseudo-element uses an irregular border-radius5px 5px 60% 60% / 5px 5px 30px 30px — which creates that elliptical dome shape you'd see on a WinXP button or an Aqua-style macOS element from around 2002. The asymmetric radius syntax is the trick that sells it. If you just use a straight rectangle for the highlight, it reads as flat.

The :active state flips the gradient direction. That's important. On the real physical buttons that Y2K interfaces were imitating, pressing something inverted the light source. The gradient flip combined with translateY(1px) gives you a tactile press that feels genuinely retro without looking broken.

In practice, text-shadow: 0 1px 1px rgba(0,0,0,0.5) on light-colored button text is doing a lot of accessibility work here. It punches the text off the gradient background enough to hit WCAG AA contrast on most saturated base colors. Don't skip it.

Bubbly and Pill-Shaped: The Aqua Variant

The Apple Aqua style — macOS 10.0 through 10.6, so roughly 2001 to 2009 — is a specific sub-genre of Y2K button design. It's rounder, more inflated, almost literally bubble-like. The highlight is larger and more pronounced, the gradient is softer, and the whole thing has a gel-capsule quality that made it look completely alien in 2001.

.aqua-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 9px 32px;
  font-family: 'Lucida Grande', 'Tahoma', sans-serif;
  font-size: 13px;
  font-weight: bold;
  color: #fff;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4);

  /* The classic Aqua blue */
  background: linear-gradient(
    180deg,
    #8ec8ff 0%,
    #2a7fff 35%,
    #0055dd 65%,
    #1a3fcc 100%
  );

  /* Fully pill-shaped */
  border-radius: 50px;
  border: 1px solid #0040b0;
  overflow: hidden;
  cursor: pointer;

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    0 2px 6px rgba(0, 60, 180, 0.5),
    0 1px 1px rgba(0, 0, 0, 0.3);
}

.aqua-btn::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 10%;
  width: 80%;
  height: 45%;
  background: radial-gradient(
    ellipse at 50% 30%,
    rgba(255, 255, 255, 0.75) 0%,
    rgba(255, 255, 255, 0.0) 80%
  );
  border-radius: 50%;
  pointer-events: none;
}

The key difference from the bevel button above is that radial-gradient ::before — it creates a softer, more organic highlight dome rather than a sharp band. And going full border-radius: 50px on the pill shape is what gives it that inflated, three-dimensional quality. The Aqua button at 36px tall with 32px horizontal padding is basically the platonic Y2K shape.

Quick aside: if you want to go further down this rabbit hole, the y2k style hub has pre-built components that already implement these patterns, so you don't have to hand-tune box-shadow stacks for an hour. But knowing how it works under the hood means you can customize the colors, sizes, and animations without fighting a black box.

Tailwind + Y2K: Making It Work in a Modern Stack

Pure CSS is great for prototyping, but if you're working in a Next.js or Remix codebase, you probably want this in a Tailwind-friendly format. Tailwind doesn't have first-class Y2K utilities, so you'll use [...] arbitrary value syntax and a thin className composition.

// Y2KButton.tsx
import { ButtonHTMLAttributes, ReactNode } from 'react';

interface Y2KButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  children: ReactNode;
  variant?: 'blue' | 'pink' | 'green';
}

const variants = {
  blue:  'from-[#5aabff] via-[#2a7fff] to-[#0055cc] border-[#003fa0] shadow-[#1a6fff]',
  pink:  'from-[#ff9eea] via-[#e040b0] to-[#aa0077] border-[#880060] shadow-[#cc2299]',
  green: 'from-[#a0ff78] via-[#44cc00] to-[#228800] border-[#116600] shadow-[#33aa00]',
} as const;

export function Y2KButton({ children, variant = 'blue', className = '', ...props }: Y2KButtonProps) {
  return (
    <button
      className={[
        'relative inline-flex items-center justify-center',
        'px-7 py-2.5 overflow-hidden',
        'text-white text-[13px] font-bold uppercase tracking-[0.5px]',
        '[text-shadow:0_1px_1px_rgba(0,0,0,0.5)]',
        `bg-gradient-to-b ${variants[variant]}`,
        'rounded-md border',
        `shadow-[inset_0_1px_0_rgba(255,255,255,0.55),inset_0_-1px_0_rgba(0,0,0,0.25),0_2px_4px_rgba(0,0,0,0.4)]`,
        'active:translate-y-px active:[background:linear-gradient(180deg,#0055cc_0%,#3a8fff_100%)]',
        'cursor-pointer transition-all duration-75',
        className,
      ].join(' ')}
      {...props}
    >
      {/* Glossy sheen layer */}
      <span
        aria-hidden="true"
        className="pointer-events-none absolute inset-x-0 top-0 h-[55%] rounded-[5px_5px_60%_60%/5px_5px_30px_30px] bg-gradient-to-b from-white/60 to-white/5"
      />
      {children}
    </button>
  );
}

The aria-hidden="true" on the sheen span is important — screen readers would otherwise try to read an empty element. And yes, those long Tailwind arbitrary shadow values are ugly. That's one situation where extracting to a @layer components block in your CSS makes more sense than keeping it inline.

Look, Tailwind's arbitrary value syntax was really designed for one-offs. If you're reusing this button across 20+ components, you'd want to define custom box-shadow tokens in tailwind.config.js under theme.extend.boxShadow. Something like 'y2k-bevel': 'inset 0 1px 0 rgba(255,255,255,0.55), ...'. Way cleaner.

One more thing — if you want the Y2K button to live alongside glassmorphism components or neumorphism cards in the same design system, you'll need to think about color token harmony. Y2K palettes are saturated to the point of aggression. You might need a separate token set or a deliberate "retro mode" toggle so the two aesthetics don't visually fight each other on the same page.

Animation: The Click Ripple and the Loading State

Static Y2K buttons are fine, but the original aesthetic had a lot of motion. Flash-era buttons bounced, shimmered, played sounds. We're not going to play sounds (please don't play sounds), but a ripple effect on click and a shimmer animation on the highlight layer get you 80% of the way to that interactive plasticity.

/* Shimmer sweep across the gloss layer */
@keyframes y2k-shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(200%); }
}

.y2k-btn::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 40%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.35) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  pointer-events: none;
}

.y2k-btn:hover::after {
  animation: y2k-shimmer 0.65s ease-in-out;
}

/* Pressed-state loading indicator */
@keyframes y2k-spin {
  to { transform: rotate(360deg); }
}

.y2k-btn[data-loading]::after {
  content: '';
  position: absolute;
  width: 12px;
  height: 12px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: #fff;
  border-radius: 50%;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  animation: y2k-spin 0.7s linear infinite;
}

The shimmer uses a ::after so the ::before stays as the static gloss dome. Two pseudo-elements, two separate jobs. The hover shimmer fires once on entry — it doesn't loop, which would be annoying — and the loading spinner replaces it when you add data-loading to the button during async operations.

That said, wrap any animation in a prefers-reduced-motion media query. The shimmer is purely decorative and should be disabled for users who are sensitive to motion:

@media (prefers-reduced-motion: reduce) {
  .y2k-btn::after { animation: none; }
}

For React implementations you'd use the gradient generator to dial in the shimmer color to match your specific button hue — the pure white shimmer shown here works on blues and purples but can look washed out on the lighter lime-green variants. Worth spending 5 minutes iterating that one value.

Color Palettes: Getting the Y2K Tone Right

Y2K color isn't just "bright." It's a specific kind of brightness — high saturation at medium-high lightness, with a slight coolness or candy quality. The reference palette you're drawing from is Windows XP's Luna theme, the original iMac G3 colors, early Macromedia Dreamweaver templates, and the icon sets that shipped with Mac OS X 10.0 in 2001.

Some starting points that work well as button base colors: ``css :root { /* Y2K electric blue — the OG */ --y2k-blue: #2a7fff; /* Candy pink — translucent iMac era */ --y2k-pink: #e040b0; /* Lime green — Windows XP taskbar vibes */ --y2k-green: #44cc00; /* Orange — Flash-era warning buttons */ --y2k-orange: #ff6600; /* Silver — the neutral, metallic default */ --y2k-silver: #b8c4d8; /* Purple — forum software, geocities */ --y2k-purple: #8833ff; } ``

Silver is the one people often skip, but it's arguably the most authentic. The default Windows 2000 and XP button was silver/grey with that beveled edge. If you're building a full Y2K theme and want it to feel grounded rather than carnival-ish, having silver as the neutral alongside the candy colors is what makes the whole palette feel period-accurate rather than just loud.

Honestly, the hardest color decision is your text. White text works on blues, pinks, and purples. On lime green and orange you'll need dark text — #003300 on green, #4d1a00 on orange — and you'll need to increase the text-shadow contrast to compensate for losing the dark-on-light contrast baseline. Test this at 13px Tahoma specifically, because that's the font that reads as Y2K. Use the box shadow generator to preview bevel combinations while you're iterating.

Putting It Together: A Full Y2K Button System

You've got the core button, the Aqua variant, Tailwind integration, animations, and a color system. What ties it into a real component library is a coherent API — size variants, disabled states, icon support, and predictable class naming.

// Full system: sizes + disabled + icon
const sizes = {
  sm: 'px-4 py-1.5 text-[11px]',
  md: 'px-7 py-2.5 text-[13px]',
  lg: 'px-10 py-3.5 text-[15px]',
};

export function Y2KButton({
  children,
  variant = 'blue',
  size = 'md',
  icon,
  disabled,
  ...props
}: Y2KButtonProps) {
  return (
    <button
      disabled={disabled}
      className={[
        'relative inline-flex items-center gap-2 overflow-hidden',
        sizes[size],
        variants[variant],
        'font-bold uppercase tracking-[0.5px] rounded-md border',
        '[text-shadow:0_1px_1px_rgba(0,0,0,0.5)]',
        disabled
          ? 'opacity-50 cursor-not-allowed'
          : 'cursor-pointer active:translate-y-px',
      ].join(' ')}
      {...props}
    >
      <span aria-hidden className="pointer-events-none absolute inset-x-0 top-0 h-[55%] rounded-[5px_5px_60%_60%/5px_5px_30px_30px] bg-gradient-to-b from-white/60 to-white/5" />
      {icon && <span className="relative z-10">{icon}</span>}
      <span className="relative z-10">{children}</span>
    </button>
  );
}

The relative z-10 on the content spans matters — without it, the ::before sheen layer (or in this case the <span> equivalent) sits on top of your text and icon in some browser/GPU combinations. Explicit stacking context keeps your content always on top of the decorative layers.

If you're building a full retro UI, browse the y2k style hub to see how buttons fit into the broader system alongside cards, badges, and form inputs. The aesthetic works best when it's consistent — a Y2K button floating inside a flat Material Design layout looks accidental rather than intentional.

FAQ

Can I use Y2K button styles with Tailwind CSS without custom plugins?

Yes — Tailwind's arbitrary value syntax handles it. You'll use [text-shadow:...], [background:...], and multi-value shadow-[...] for the bevel stacks. It's verbose inline but works fine without any plugin.

What font pairs best with Y2K button design?

Tahoma and Verdana are the most authentic choices — they were the default UI fonts in Windows XP and early web apps. Lucida Grande works for the macOS Aqua variant. Avoid anything too modern or variable-weight.

How do I make a Y2K button accessible?

Always add text-shadow for contrast, mark decorative pseudo-elements or spans with aria-hidden, and make sure your base color passes WCAG AA at the font size you're using. The silver variant is often safer than the bright candy colors.

Is Y2K design the same as neumorphism or glassmorphism?

No — they're separate styles with different aesthetics. Neumorphism uses soft same-color shadows; glassmorphism uses blur and transparency. Y2K is hard bevels, saturated color fills, and glossy highlight overlays. They can coexist in a design system but serve different moods.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Y2K Loading Animation in CSS: Spinners, Progress Bars and Digital ClocksY2K UI Design: Why the Year 2000 Aesthetic Is Back and BiggerCSS Box Shadow: The Complete Guide With Live ExamplesLiquid Fill Button Animation in CSS: SVG and clip-path Morph