button · neumorphic · 灵感来源:Neumorphism(同色双阴影:亮侧高光 + 暗侧投影)
Neumorphic Button · 拟态按钮 · 按钮 / Neumorphic
做一个 Neumorphic Button(拟态按钮):
- 控件与页面同色,只靠亮侧高光 + 暗侧投影浮出表面
- 没有边框、没有模糊,凹陷与浮起是同一组阴影的两种朝向
- sm / md / lg 三档尺寸 + pill 变体 + disabled
- 与玻璃按钮同一份 props,暗色跟随 --neuro-bg 自动换色
用这些 CSS 变量:--color-text --color-text-muted --color-text-subtle --dur-fast --ease-standard --glass-border --glass-recess --neuro-bg --neuro-dark --neuro-light --neuro-shadow --text-base --text-xs
灵感参考:Neumorphism(同色双阴影:亮侧高光 + 暗侧投影)--color-text --color-text-muted --color-text-subtle --dur-fast --ease-standard --glass-border --glass-recess --neuro-bg --neuro-dark --neuro-light --neuro-shadow --text-base --text-xs
焦点 / 按压 / 禁用
(尚未接进整页场景)
/r/button--neumorphic.json
免外壳的 markdown 版:/llms/components/button--neumorphic.md--neuro-bg 与页面底色几乎一致,只靠 --neuro-shadow(亮侧 + 暗侧成对)浮出表面inset——表面被压进去,配合全站统一的 0.97 缩放--neuro-bg 换成 #23252b,双阴影跟着变,不用改组件限制:拟态的可点性全靠光影,低对比背景上会"看不出能点"(library.md 的示能判据)——所以只在 demo 卡片这种明确语境里用。
import { type ButtonHTMLAttributes, type ReactNode } from 'react';
import { Spinner } from '~/components/Feedback';
type Variant = 'default' | 'pill';
type Size = 'sm' | 'md' | 'lg';
interface NeumorphicButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
variant?: Variant;
size?: Size;
/** 加载中:转圈 + 禁用 + aria-busy(宽度不变,内容变淡) */
loading?: boolean;
}
const sizeClasses: Record<Size, string> = {
sm: 'px-4 py-1.5 text-sm',
md: 'px-6 py-2.5 text-base',
lg: 'px-8 py-3.5 text-lg',
};
const variantClasses: Record<Variant, string> = {
default: 'rounded-xl',
pill: 'rounded-full',
};
/**
* 拟态按钮:同色浮起,没有边框也没有模糊。
* 亮侧高光 + 暗侧投影成对出现(--neuro-shadow),按下时翻成 inset——
* 这是新拟态唯一"物理"的地方:表面被压进去,而不是缩小。
*/
export function NeumorphicButton({
children,
variant = 'default',
size = 'md',
loading = false,
className = '',
...rest
}: NeumorphicButtonProps) {
const spinnerSize = size === 'lg' ? 16 : size === 'md' ? 14 : 12;
return (
<button
{...rest}
aria-busy={loading || undefined}
disabled={loading || rest.disabled}
className={`
relative inline-flex items-center justify-center
${sizeClasses[size]}
${variantClasses[variant]}
font-medium
text-[var(--color-text)]
bg-[var(--neuro-bg)]
shadow-[var(--neuro-shadow)]
press-feedback
transition-[box-shadow,transform,opacity] duration-[var(--dur-fast)] ease-[var(--ease-standard)]
active:shadow-[inset_3px_3px_8px_var(--neuro-dark),inset_-3px_-3px_8px_var(--neuro-light)]
disabled:opacity-40 disabled:cursor-not-allowed
cursor-pointer
${className}
`}
>
{loading && <Spinner size={spinnerSize} className="mr-2 align-[-0.125em]" label="加载中" />}
<span className={loading ? 'opacity-60' : ''}>{children}</span>
</button>
);
}
src/components/button/neumorphic/