Buttons buttonneoncyberpunkglowborder
Neon Border Button
A futuristic cyber button with rotating conical neon laser border and glow reflection.
Preview
Usage
Copy the full block below to use this component with its imports.
astro
---
import { NeonBorderButton } from 'astro-component-kit';
---
<NeonBorderButton variant="cyan" size="md">Neon Action</NeonBorderButton> ---
import { NeonBorderButton } from 'astro-component-kit';
---
<NeonBorderButton variant="cyan" size="md">Neon Action</NeonBorderButton> Manual Installation
If you are not using the npm package, create a new file src/components/lib/NeonBorderButton.astro and paste the following code:
astro
---
/**
* NeonBorderButton — Futuristic button wrapped in an animated rotating neon conic-gradient border.
*
* @param {string} color - Primary neon hue (e.g. '#fbbf24', '#38bdf8', '#a855f7'). Default is '#d4a017'.
* @param {'sm' | 'md' | 'lg'} size - Button size variant. Default is 'md'.
* @param {string} href - Optional link destination.
* @param {boolean} disabled - Whether the button is disabled.
* @param {string} className - Additional CSS class names.
*/
interface Props {
color?: string;
size?: 'sm' | 'md' | 'lg';
href?: string;
disabled?: boolean;
className?: string;
}
const {
color = '#d4a017',
size = 'md',
href,
disabled = false,
className = '',
} = Astro.props;
const Tag = href ? 'a' : 'button';
---
<Tag
href={href}
disabled={!href && disabled ? true : undefined}
aria-disabled={disabled ? 'true' : undefined}
class:list={['neon-border-btn', `neon-border-btn--${size}`, className]}
style={`--neon-accent: ${color};`}
>
<span class="neon-border-btn__glow" aria-hidden="true"></span>
<span class="neon-border-btn__border" aria-hidden="true"></span>
<span class="neon-border-btn__content">
<slot />
</span>
</Tag>
<style>
.neon-border-btn {
--neon-speed: 4s;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: none;
background: transparent;
border-radius: var(--r-md, 12px);
cursor: pointer;
user-select: none;
transition: transform 0.25s ease;
}
.neon-border-btn:hover {
transform: translateY(-2px);
}
.neon-border-btn:focus-visible {
outline: 2px solid var(--neon-accent);
outline-offset: 4px;
}
.neon-border-btn[disabled],
.neon-border-btn[aria-disabled="true"] {
opacity: 0.45;
cursor: not-allowed;
pointer-events: none;
}
/* Sizes */
.neon-border-btn--sm .neon-border-btn__content {
padding: 0.45rem 1rem;
font-size: 0.8rem;
}
.neon-border-btn--md .neon-border-btn__content {
padding: 0.75rem 1.6rem;
font-size: 0.92rem;
}
.neon-border-btn--lg .neon-border-btn__content {
padding: 1rem 2.2rem;
font-size: 1.05rem;
}
/* Border track & animated beam */
.neon-border-btn__border {
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1.5px;
background: conic-gradient(
from 0deg,
transparent 0deg,
transparent 90deg,
var(--neon-accent) 180deg,
transparent 270deg,
transparent 360deg
);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask-composite: xor;
animation: neon-spin var(--neon-speed) linear infinite;
pointer-events: none;
}
.neon-border-btn__glow {
position: absolute;
inset: -3px;
border-radius: inherit;
background: radial-gradient(circle, var(--neon-accent), transparent 70%);
opacity: 0;
filter: blur(14px);
transition: opacity 0.3s ease;
pointer-events: none;
}
.neon-border-btn:hover .neon-border-btn__glow {
opacity: 0.5;
}
.neon-border-btn__content {
position: relative;
z-index: 2;
display: inline-flex;
align-items: center;
gap: var(--sp-2, 0.5rem);
width: 100%;
height: 100%;
background: rgba(10, 10, 12, 0.95);
border-radius: inherit;
color: var(--c-text-1, #ffffff);
font-family: var(--font-sans, system-ui, sans-serif);
font-weight: 700;
letter-spacing: 0.03em;
backdrop-filter: blur(16px);
transition: background 0.2s ease, color 0.2s ease;
}
.neon-border-btn:hover .neon-border-btn__content {
background: rgba(16, 16, 20, 0.98);
color: var(--neon-accent);
}
@keyframes neon-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.neon-border-btn__border {
animation: none;
background: var(--neon-accent);
}
}
</style> ---
/**
* NeonBorderButton — Futuristic button wrapped in an animated rotating neon conic-gradient border.
*
* @param {string} color - Primary neon hue (e.g. '#fbbf24', '#38bdf8', '#a855f7'). Default is '#d4a017'.
* @param {'sm' | 'md' | 'lg'} size - Button size variant. Default is 'md'.
* @param {string} href - Optional link destination.
* @param {boolean} disabled - Whether the button is disabled.
* @param {string} className - Additional CSS class names.
*/
interface Props {
color?: string;
size?: 'sm' | 'md' | 'lg';
href?: string;
disabled?: boolean;
className?: string;
}
const {
color = '#d4a017',
size = 'md',
href,
disabled = false,
className = '',
} = Astro.props;
const Tag = href ? 'a' : 'button';
---
<Tag
href={href}
disabled={!href && disabled ? true : undefined}
aria-disabled={disabled ? 'true' : undefined}
class:list={['neon-border-btn', `neon-border-btn--${size}`, className]}
style={`--neon-accent: ${color};`}
>
<span class="neon-border-btn__glow" aria-hidden="true"></span>
<span class="neon-border-btn__border" aria-hidden="true"></span>
<span class="neon-border-btn__content">
<slot />
</span>
</Tag>
<style>
.neon-border-btn {
--neon-speed: 4s;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: none;
background: transparent;
border-radius: var(--r-md, 12px);
cursor: pointer;
user-select: none;
transition: transform 0.25s ease;
}
.neon-border-btn:hover {
transform: translateY(-2px);
}
.neon-border-btn:focus-visible {
outline: 2px solid var(--neon-accent);
outline-offset: 4px;
}
.neon-border-btn[disabled],
.neon-border-btn[aria-disabled="true"] {
opacity: 0.45;
cursor: not-allowed;
pointer-events: none;
}
/* Sizes */
.neon-border-btn--sm .neon-border-btn__content {
padding: 0.45rem 1rem;
font-size: 0.8rem;
}
.neon-border-btn--md .neon-border-btn__content {
padding: 0.75rem 1.6rem;
font-size: 0.92rem;
}
.neon-border-btn--lg .neon-border-btn__content {
padding: 1rem 2.2rem;
font-size: 1.05rem;
}
/* Border track & animated beam */
.neon-border-btn__border {
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1.5px;
background: conic-gradient(
from 0deg,
transparent 0deg,
transparent 90deg,
var(--neon-accent) 180deg,
transparent 270deg,
transparent 360deg
);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask-composite: xor;
animation: neon-spin var(--neon-speed) linear infinite;
pointer-events: none;
}
.neon-border-btn__glow {
position: absolute;
inset: -3px;
border-radius: inherit;
background: radial-gradient(circle, var(--neon-accent), transparent 70%);
opacity: 0;
filter: blur(14px);
transition: opacity 0.3s ease;
pointer-events: none;
}
.neon-border-btn:hover .neon-border-btn__glow {
opacity: 0.5;
}
.neon-border-btn__content {
position: relative;
z-index: 2;
display: inline-flex;
align-items: center;
gap: var(--sp-2, 0.5rem);
width: 100%;
height: 100%;
background: rgba(10, 10, 12, 0.95);
border-radius: inherit;
color: var(--c-text-1, #ffffff);
font-family: var(--font-sans, system-ui, sans-serif);
font-weight: 700;
letter-spacing: 0.03em;
backdrop-filter: blur(16px);
transition: background 0.2s ease, color 0.2s ease;
}
.neon-border-btn:hover .neon-border-btn__content {
background: rgba(16, 16, 20, 0.98);
color: var(--neon-accent);
}
@keyframes neon-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.neon-border-btn__border {
animation: none;
background: var(--neon-accent);
}
}
</style>
Quick Info
- Category
- Buttons
- Filename
NeonBorderButton.astro- Dependencies
- None — pure Astro + CSS
- Tags
- buttonneoncyberpunkglowborder