@daniesy/beam-border
v1.0.1
Published
Vue 3 port of border-beam animated border glow effect
Maintainers
Readme
@daniesy/beam-border
Animated border beam effect for Vue 3. This package is a Vue port of Jakub Antalik's original React border-beam project, adapted to Vue single-file components, Composition API composables, Vue events, and Vue fallthrough attributes.
It adds a traveling or breathing glow around any slotted element: cards, buttons, inputs, search bars, panels, and other UI surfaces.
Install
npm install @daniesy/beam-borderVue is a peer dependency:
npm install vueQuick start
<script setup lang="ts">
import { BorderBeam } from "@daniesy/beam-border";
</script>
<template>
<BorderBeam>
<div class="card">Your content here</div>
</BorderBeam>
</template>
<style scoped>
.card {
border-radius: 16px;
padding: 32px;
background: #1d1d1d;
}
</style>The component wraps your default slot and overlays the animated beam effect. It auto-detects the border-radius of the first slotted element unless you pass border-radius explicitly.
Types
Built-in presets control the glow style and motion. They fall into two families.
Rotate
<template>
<BorderBeam size="md">
<Card />
</BorderBeam>
<BorderBeam size="sm">
<IconButton />
</BorderBeam>
<BorderBeam size="line">
<SearchBar />
</BorderBeam>
</template>mdis the default full border glow.smis tuned for compact elements.lineis a bottom-only traveling glow.
Pulse
<template>
<BorderBeam size="pulse-inner">
<Card />
</BorderBeam>
<BorderBeam size="pulse-outside">
<Card />
</BorderBeam>
</template>pulse-inneris a contained breathing border glow.pulse-outsideis an outward-blooming halo around the element.
Both pulse types support all color variants, strength, theme, tint, and duration. Pulse defaults to a 2.3 second cycle.
pulse-outside needs an opaque wrapped child. The colorful core and halo render behind your content and bloom outward, so transparent children will show the inner glow. The wrapper uses overflow: visible, so surrounding layout should allow the halo to spill.
pulse-outside expects your child to provide its own subtle 1px edge, such as a border or box-shadow: inset 0 0 0 1px. This keeps the idle edge defined without double-painting the border.
Color variants
<template>
<BorderBeam color-variant="colorful" />
<BorderBeam color-variant="mono" />
<BorderBeam color-variant="ocean" />
<BorderBeam color-variant="sunset" />
</template>colorfulis the default rainbow spectrum.monois grayscale.oceanuses blue and purple tones.sunsetuses orange, yellow, and red tones.
All variants except mono animate through a hue-shift cycle unless static-colors is enabled.
Custom tint
Use tint when you want the original motion geometry with one custom color.
<template>
<BorderBeam size="pulse-outside" tint="#22c55e">
<div class="card">Custom tinted pulse</div>
</BorderBeam>
</template>When tint is provided, colors are treated as static so the custom color stays stable.
Theme
<template>
<BorderBeam theme="dark" />
<BorderBeam theme="light" />
<BorderBeam theme="auto" />
</template>darkis the default.lightadapts opacity and contrast for light backgrounds.autofollowsprefers-color-scheme.
Strength
Control the overall intensity without affecting the wrapped content.
<template>
<BorderBeam :strength="0.7">
<Card />
</BorderBeam>
</template>strength accepts 0 to 1. Internally, max strength is visually amplified so 1 reads clearly in real UI.
CSS tuning hooks
Use CSS custom properties in the component's style attribute to tune generated layers without replacing their filters or animation logic.
<template>
<BorderBeam
size="pulse-outside"
:style="{
'--pulse-glow-boost': 1.2,
'--beam-core-blur': '8px',
'--beam-bloom-opacity': 0.7,
}"
>
<Card />
</BorderBeam>
</template>| Custom property | Default | Applies to | Description |
| --------------- | ------- | ---------- | ----------- |
| --pulse-glow-boost | 1 | Pulse variants | Relative multiplier for pulse glow reach |
| --beam-hue-base | 0deg | All variants | Base hue offset applied before animation |
| --beam-stroke-opacity | 1 | All variants | Multiplier for the crisp beam stroke |
| --beam-inner-opacity | 1 | All variants | Multiplier for the inner glow layer |
| --beam-bloom-opacity | 1 | All variants | Multiplier for the outer bloom layer |
| --beam-core-blur | theme preset | pulse-outside | Blur radius for the outward core glow |
| --beam-bloom-blur | theme preset | pulse-outside | Blur radius for the outward halo |
| --beam-glow-brightness | resolved brightness | pulse-outside | Brightness for core and halo filters |
| --beam-glow-saturate | resolved saturation | pulse-outside | Saturation for core and halo filters |
Play and pause
<script setup lang="ts">
import { shallowRef } from "vue";
import { BorderBeam } from "@daniesy/beam-border";
const active = shallowRef(true);
</script>
<template>
<BorderBeam :active="active" @deactivate="console.log('faded out')">
<Card />
</BorderBeam>
</template>The component fades in and out smoothly when active changes. It also pauses animation work when the wrapper is offscreen.
Props
| Prop | Type | Default | Description |
| -------------------------------- | ------------------------------------------------------------ | -------------- | ----------------------------------- |
| size | 'sm' \| 'md' \| 'line' \| 'pulse-outside' \| 'pulse-inner' | 'md' | Size/type preset |
| colorVariant / color-variant | 'colorful' \| 'mono' \| 'ocean' \| 'sunset' | 'colorful' | Color palette |
| theme | 'dark' \| 'light' \| 'auto' | 'dark' | Background adaptation |
| strength | number | 1 | Effect intensity, clamped to 0..1 |
| duration | number | per size | Animation cycle duration in seconds |
| active | boolean | true | Whether the animation is active |
| borderRadius / border-radius | number | auto-detected | Custom border radius in px |
| brightness | number | per size/theme | Glow brightness multiplier |
| saturation | number | per size/theme | Glow saturation multiplier |
| hueRange / hue-range | number | 30 | Hue rotation range in degrees |
| tint | string | undefined | Custom single-color beam tint |
| staticColors / static-colors | boolean | false | Disable hue-shift animation |
All standard fallthrough attributes are forwarded to the wrapper element. That includes id, class, style, ARIA attributes, and DOM listeners.
Events
| Event | Description |
| -------------- | ------------------------------------- |
| activate | Emitted when fade-in completes |
| deactivate | Emitted when fade-out completes |
| animationend | Re-emits the wrapper AnimationEvent |
How it works
BorderBeam renders a wrapper div with:
::after: the beam stroke::before: the inner glow layer[data-beam-bloom]: the outer bloom/glow child
The effect layers are absolutely positioned and use pointer-events: none, so they do not block interaction with your slot content.
Styles are injected into document.head as shared, reference-counted stylesheets: one static base sheet (keyframes and @property registrations) plus one sheet per configuration (size × theme × color variant × static colors × tint). Any number of beams with the same configuration share a single <style> element, and per-instance values (radius, duration, opacity, brightness, hue range, …) flow in through CSS custom properties set inline on the wrapper — no <style> elements are rendered in your markup.
Rotate and line modes animate with CSS custom properties and keyframes. Pulse modes use a shared, frame-rate-capped requestAnimationFrame driver exposed through Vue composables, so multiple instances share one loop and can pause when inactive, offscreen, or reduced motion is preferred.
SSR note: stylesheets attach on the client (module code guards on
document), so server-rendered markup paints the beam once the app hydrates.
Project structure
beam-border/
├── src/
│ ├── index.ts
│ ├── components/BorderBeam.vue
│ ├── composables/
│ ├── pulseDriver.ts
│ ├── styles.ts
│ └── types.ts
├── tests/
├── dist/
├── .github/workflows/publish.yml
├── package.json
└── README.mdRequirements
- Vue
>=3.5.0 - Modern browser support for CSS masks, gradients, custom properties, and
ResizeObserver
Development
npm install
npm run dev
npm run typecheck
npm run test
npm run build
npm run pack:dry-runThe demo runs with Vite:
npm run devPublishing to npm with GitHub Actions
This repo includes .github/workflows/publish.yml. It uses GitHub Actions and npm directly, with no paid third-party publishing service.
To publish:
- Create an npm automation token.
- Add it to your GitHub repository secrets as
NPM_TOKEN. - Make sure
package.jsonhas the package name and version you want to publish. - Publish a GitHub release, or run the workflow manually from the Actions tab.
The workflow runs npm ci, npm run typecheck, npm run test, npm run build, then npm publish --access public --provenance.
Attribution
This is a Vue 3 port of Jakubantalik/border-beam. The visual behavior and API concepts come from the original React package; this version adapts them to Vue slots, Vue props/events, and composables.
License
MIT. See LICENSE.
