primitives
Neu Icon Button
A 100 × 100 neumorphic square button with a floating SVG icon in the top-right and an engraved text label at the bottom. Outset token-derived shadows create a soft raised surface; hover scales the square up and deepens shadows while the icon and label float upward with color shifts; active press inverts to an inset concave shadow with a slight shrink. Token-alpha shadow mixing adapts automatically between light and dark themes with no hardcoded hex. Accessible: native button semantics, configurable aria-label (defaults to the visible label), and a focus-visible ring from --ring.
Ported from Cksunandh (MIT)
Install
1. Register the namespace (once per project):
json
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
bash
npx shadcn add @bottega/neu-icon-buttonUsage
Usagetsx
<NeuIconButton />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | React.ReactNode | — | SVG element rendered in the top-right slot of the square. Defaults to the integration cross-mark from the original uiverse design. Should be aria-hidden (decorative); the button's accessible name comes from aria-label. |
| label | string | command | Visible text label at the bottom of the square. Defaults to "command". Also used as the aria-label when no explicit aria-label is passed. |
Source
neu-icon-button.tsxtsx
"use client";
import type React from "react";
import styles from "./neu-icon-button.module.css";
/** Default integration/cross SVG from the original uiverse component (MIT). */
const DEFAULT_ICON = (
<svg
aria-hidden="true"
viewBox="0 0 80 80"
width="16"
height="16"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M64,48L64,48h-8V32h8c8.836,0,16-7.164,16-16S72.836,0,64,0c-8.837,0-16,7.164-16,16v8H32v-8c0-8.836-7.164-16-16-16S0,7.164,0,16s7.164,16,16,16h8v16h-8l0,0l0,0C7.164,48,0,55.164,0,64s7.164,16,16,16c8.837,0,16-7.164,16-16l0,0v-8h16v7.98c0,0.008-0.001,0.014-0.001,0.02c0,8.836,7.164,16,16,16s16-7.164,16-16S72.836,48.002,64,48z M64,8c4.418,0,8,3.582,8,8s-3.582,8-8,8h-8v-8C56,11.582,59.582,8,64,8z M8,16c0-4.418,3.582-8,8-8s8,3.582,8,8v8h-8C11.582,24,8,20.417,8,16z M16,72c-4.418,0-8-3.582-8-8s3.582-8,8-8l0,0h8v8C24,68.418,20.418,72,16,72z M32,48V32h16v16H32z M64,72c-4.418,0-8-3.582-8-8l0,0v-8h7.999c4.418,0,8,3.582,8,8S68.418,72,64,72z" />
</svg>
);
export interface NeuIconButtonProps extends React.ComponentProps<"button"> {
/**
* SVG element rendered in the top-right slot of the square.
* Defaults to the integration cross-mark from the original uiverse design.
* Should be aria-hidden (decorative); the button's accessible name comes from aria-label.
*/
icon?: React.ReactNode;
/**
* Visible text label at the bottom of the square.
* Defaults to "command". Also used as the aria-label when no explicit aria-label is passed.
*/
label?: string;
}
export function NeuIconButton({
icon,
label = "command",
className,
"aria-label": ariaLabel,
...props
}: NeuIconButtonProps) {
return (
<button
type="button"
aria-label={ariaLabel ?? label}
className={[styles.button, className].filter(Boolean).join(" ")}
{...props}
>
{/* aria-hidden: the button name comes from aria-label; inner content is decorative */}
<div className={styles.content} aria-hidden="true">
<div className={styles.icon}>{icon ?? DEFAULT_ICON}</div>
<p className={styles.text}>{label}</p>
</div>
</button>
);
}
Dependencies
- @bottega/tokens