Buttons buttonneumorphismsoft ui

Neumorphic Button

A premium Soft UI button with beveled surface gradients and hue-tinted shadows.

Preview

Usage

Copy the full block below to use this component with its imports.

astro
---
import { NeumorphicButton } from 'astro-component-kit';
---

<NeumorphicButton 
  baseColor="#6366f1" 
  variant="convex" 
  size="lg"
>
  Confirm Action
</NeumorphicButton>
--- import { NeumorphicButton } from 'astro-component-kit'; --- <NeumorphicButton baseColor="#6366f1" variant="convex" size="lg" > Confirm Action </NeumorphicButton>

Manual Installation

If you are not using the npm package, create a new file src/components/lib/NeumorphicButton.astro and paste the following code:

astro
---
interface Props {
  href?: string;
  type?: 'button' | 'submit' | 'reset';
  baseColor?: string;
  variant?: 'convex' | 'concave';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
}

const { 
  href, 
  type = 'button', 
  baseColor = 'var(--c-bg-2, #111827)',
  variant = 'convex',
  size = 'md',
  disabled = false 
} = Astro.props;

const Tag = href ? 'a' : 'button';
---

<Tag 
  href={href} 
  type={href ? undefined : type}
  disabled={disabled}
  class={`neu-btn neu-btn--${size} neu-btn--${variant}`}
  style={`--base-color: ${baseColor}`}
>
  <span class="neu-btn__surface">
    <slot />
  </span>
</Tag>

<style>
  .neu-btn {
    /* Design Tokens */
    --distance: 8px;
    --blur: 16px;
    --shadow-opacity: 0.4;
    --highlight-opacity: 0.05;
    
    /* Derived Colors */
    --shadow-dark: rgba(0, 0, 0, var(--shadow-opacity));
    --shadow-light: rgba(255, 255, 255, var(--highlight-opacity));
    --surface-light: color-mix(in srgb, var(--base-color), white 4%);
    --surface-dark: color-mix(in srgb, var(--base-color), black 4%);
    
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    background: transparent;
    cursor: pointer;
    text-decoration: none;
    transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    font-family: var(--font-sans, inherit);
    font-weight: 700;
    letter-spacing: 0.3px;
    color: var(--c-text-1, #f1f5f9);
    border-radius: var(--r-xl, 20px);
    user-select: none;
    white-space: nowrap;
    outline: none;
  }

  .neu-btn__surface {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: inherit;
    border-radius: inherit;
    background: linear-gradient(145deg, var(--surface-light), var(--surface-dark));
    z-index: 2;
    transition: background 0.4s ease, box-shadow 0.4s ease;
  }

  /* Rim Light highlight */
  .neu-btn__surface::before {
    content: '';
    position: absolute;
    inset: 0.5px;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(135deg, rgba(255,255,255,0.08), transparent 50%, rgba(0,0,0,0.1));
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    mask-composite: exclude;
    pointer-events: none;
  }

  /* Convex Variant (Default) */
  .neu-btn--convex {
    box-shadow: 
      var(--distance) var(--distance) var(--blur) var(--shadow-dark),
      calc(var(--distance) * -0.6) calc(var(--distance) * -0.6) var(--blur) var(--shadow-light);
  }

  /* Concave Variant */
  .neu-btn--concave .neu-btn__surface {
    background: var(--base-color);
    box-shadow: 
      inset var(--distance) var(--distance) var(--blur) var(--shadow-dark),
      inset calc(var(--distance) * -0.5) calc(var(--distance) * -0.5) var(--blur) var(--shadow-light);
  }

  /* Hover States */
  .neu-btn:hover:not(:disabled) {
    transform: translateY(-2px);
    filter: brightness(1.08);
  }

  .neu-btn--convex:hover:not(:disabled) {
    box-shadow: 
      calc(var(--distance) * 1.4) calc(var(--distance) * 1.4) calc(var(--blur) * 1.4) var(--shadow-dark),
      calc(var(--distance) * -0.8) calc(var(--distance) * -0.8) calc(var(--blur) * 1.4) var(--shadow-light);
  }

  /* Active/Pressed States */
  .neu-btn:active:not(:disabled) {
    transform: translateY(0) scale(0.97);
  }

  .neu-btn--convex:active:not(:disabled) .neu-btn__surface {
    background: var(--base-color);
    box-shadow: 
      inset calc(var(--distance) * 0.5) calc(var(--distance) * 0.5) calc(var(--blur) * 0.5) var(--shadow-dark),
      inset calc(var(--distance) * -0.3) calc(var(--distance) * -0.3) calc(var(--blur) * 0.5) var(--shadow-light);
  }

  .neu-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    filter: grayscale(1);
    box-shadow: none;
  }

  /* Sizes */
  .neu-btn--sm { padding: 0.5rem 1.4rem; font-size: 0.85rem; --distance: 4px; --blur: 8px; }
  .neu-btn--md { padding: 0.85rem 2.4rem; font-size: 1rem; }
  .neu-btn--lg { padding: 1.2rem 3.5rem; font-size: 1.2rem; --distance: 12px; --blur: 24px; }

  /* Color scheme adjustments */
  @media (prefers-color-scheme: light) {
    .neu-btn {
      --shadow-opacity: 0.15;
      --highlight-opacity: 0.8;
      --shadow-dark: rgba(163, 177, 198, var(--shadow-opacity));
      --shadow-light: rgba(255, 255, 255, var(--highlight-opacity));
      color: #374151;
    }
  }
</style>
--- interface Props { href?: string; type?: 'button' | 'submit' | 'reset'; baseColor?: string; variant?: 'convex' | 'concave'; size?: 'sm' | 'md' | 'lg'; disabled?: boolean; } const { href, type = 'button', baseColor = 'var(--c-bg-2, #111827)', variant = 'convex', size = 'md', disabled = false } = Astro.props; const Tag = href ? 'a' : 'button'; --- <Tag href={href} type={href ? undefined : type} disabled={disabled} class={`neu-btn neu-btn--${size} neu-btn--${variant}`} style={`--base-color: ${baseColor}`} > <span class="neu-btn__surface"> <slot /> </span> </Tag> <style> .neu-btn { /* Design Tokens */ --distance: 8px; --blur: 16px; --shadow-opacity: 0.4; --highlight-opacity: 0.05; /* Derived Colors */ --shadow-dark: rgba(0, 0, 0, var(--shadow-opacity)); --shadow-light: rgba(255, 255, 255, var(--highlight-opacity)); --surface-light: color-mix(in srgb, var(--base-color), white 4%); --surface-dark: color-mix(in srgb, var(--base-color), black 4%); position: relative; display: inline-flex; align-items: center; justify-content: center; padding: 0; border: none; background: transparent; cursor: pointer; text-decoration: none; transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); font-family: var(--font-sans, inherit); font-weight: 700; letter-spacing: 0.3px; color: var(--c-text-1, #f1f5f9); border-radius: var(--r-xl, 20px); user-select: none; white-space: nowrap; outline: none; } .neu-btn__surface { position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; padding: inherit; border-radius: inherit; background: linear-gradient(145deg, var(--surface-light), var(--surface-dark)); z-index: 2; transition: background 0.4s ease, box-shadow 0.4s ease; } /* Rim Light highlight */ .neu-btn__surface::before { content: ''; position: absolute; inset: 0.5px; border-radius: inherit; padding: 1px; background: linear-gradient(135deg, rgba(255,255,255,0.08), transparent 50%, rgba(0,0,0,0.1)); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude; pointer-events: none; } /* Convex Variant (Default) */ .neu-btn--convex { box-shadow: var(--distance) var(--distance) var(--blur) var(--shadow-dark), calc(var(--distance) * -0.6) calc(var(--distance) * -0.6) var(--blur) var(--shadow-light); } /* Concave Variant */ .neu-btn--concave .neu-btn__surface { background: var(--base-color); box-shadow: inset var(--distance) var(--distance) var(--blur) var(--shadow-dark), inset calc(var(--distance) * -0.5) calc(var(--distance) * -0.5) var(--blur) var(--shadow-light); } /* Hover States */ .neu-btn:hover:not(:disabled) { transform: translateY(-2px); filter: brightness(1.08); } .neu-btn--convex:hover:not(:disabled) { box-shadow: calc(var(--distance) * 1.4) calc(var(--distance) * 1.4) calc(var(--blur) * 1.4) var(--shadow-dark), calc(var(--distance) * -0.8) calc(var(--distance) * -0.8) calc(var(--blur) * 1.4) var(--shadow-light); } /* Active/Pressed States */ .neu-btn:active:not(:disabled) { transform: translateY(0) scale(0.97); } .neu-btn--convex:active:not(:disabled) .neu-btn__surface { background: var(--base-color); box-shadow: inset calc(var(--distance) * 0.5) calc(var(--distance) * 0.5) calc(var(--blur) * 0.5) var(--shadow-dark), inset calc(var(--distance) * -0.3) calc(var(--distance) * -0.3) calc(var(--blur) * 0.5) var(--shadow-light); } .neu-btn:disabled { opacity: 0.4; cursor: not-allowed; filter: grayscale(1); box-shadow: none; } /* Sizes */ .neu-btn--sm { padding: 0.5rem 1.4rem; font-size: 0.85rem; --distance: 4px; --blur: 8px; } .neu-btn--md { padding: 0.85rem 2.4rem; font-size: 1rem; } .neu-btn--lg { padding: 1.2rem 3.5rem; font-size: 1.2rem; --distance: 12px; --blur: 24px; } /* Color scheme adjustments */ @media (prefers-color-scheme: light) { .neu-btn { --shadow-opacity: 0.15; --highlight-opacity: 0.8; --shadow-dark: rgba(163, 177, 198, var(--shadow-opacity)); --shadow-light: rgba(255, 255, 255, var(--highlight-opacity)); color: #374151; } } </style>

Quick Info

Category
Buttons
Filename
NeumorphicButton.astro
Dependencies
None — pure Astro + CSS
Tags
buttonneumorphismsoft ui