Note: This article was originally published on my personal blog. For more updates, follow me on Twitter @ibelick.
I recently saw this effect this effect on the Base Hub website and I wanted to recreate it.
I'm providing a solution with React and Tailwind CSS, and one with CSS. I'll also explore more UI effects using this technique.
The component
React and Tailwind CSS solution
export const InputSpotlightBorder = () => {
const divRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [opacity, setOpacity] = useState(0);
const handleMouseMove = (e: React.MouseEvent<HTMLInputElement>) => {
if (!divRef.current || isFocused) return;
const div = divRef.current;
const rect = div.getBoundingClientRect();
setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
};
const handleFocus = () => {
setIsFocused(true);
setOpacity(1);
};
const handleBlur = () => {
setIsFocused(false);
setOpacity(0);
};
const handleMouseEnter = () => {
setOpacity(1);
};
const handleMouseLeave = () => {
setOpacity(0);
};
return (
<>
<div className="relative w-80">
<input
onMouseMove={handleMouseMove}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
autoComplete="off"
placeholder="Enter your email address"
type="email"
name="email"
className="bg-neutral-950 h-12 w-full cursor-default rounded-md border border-slate-800 p-3.5 text-slate-100 transition-colors duration-500 placeholder:select-none placeholder:text-neutral-500 focus:border-[#E47320] focus:outline-none"
/>
<input
ref={divRef}
disabled
style={{
border: "1px solid rgb(228 115 32)",
opacity,
WebkitMaskImage: `radial-gradient(30% 30px at ${position.x}px ${position.y}px, black 45%, transparent)`,
}}
aria-hidden="true"
className="border-[rgb(228 115 32)] pointer-events-none absolute left-0 top-0 z-10 h-12 w-full cursor-default rounded-md border bg-[transparent] p-3.5 opacity-0 transition-opacity duration-500 placeholder:select-none"
/>
</div>
</>
);
};
The spotlight border effect is achieved by overlaying a second disabled input element with the same dimensions as the original input, positioned absolutely on top of the original input.
The effect is created by applying a radial-gradient mask to the overlay input's border. The gradient mask has a transparent outer region, creating a spotlight-like appearance. The component uses event listeners to handle mouse movement, focus, and blur events.
When the mouse moves over the input, the handleMouseMove function updates the position of the gradient mask based on the current mouse coordinates relative to the input element. The spotlight effect follows the mouse as a result.
When the input gains focus (handleFocus
), the spotlight effect's opacity is set to 1, making it fully visible. When the input loses focus (handleBlur
), the opacity is set back to 0, making the spotlight invisible. The onMouseEnter
and onMouseLeave
events ensure that the spotlight is only visible when the mouse is inside the input area.
React + CSS
If you don't want to use Tailwind CSS, you can use the following CSS to achieve the same effect.
import React, { useRef, useState } from "react";
import "./spotlightBorder.css";
export const InputSpotlightBorderCSS = () => {
const divRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [opacity, setOpacity] = useState(0);
const handleMouseMove = (e: React.MouseEvent<HTMLInputElement>) => {
if (!divRef.current || isFocused) return;
const div = divRef.current;
const rect = div.getBoundingClientRect();
setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
};
const handleFocus = () => {
setIsFocused(true);
setOpacity(1);
};
const handleBlur = () => {
setIsFocused(false);
setOpacity(0);
};
const handleMouseEnter = () => {
setOpacity(1);
};
const handleMouseLeave = () => {
setOpacity(0);
};
return (
<div className="input-wrapper relative">
<input
onMouseMove={handleMouseMove}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
autoComplete="off"
placeholder="Enter your email address"
type="email"
name="email"
className="base-input"
/>
<input
ref={divRef}
disabled
style={{
border: "1px solid rgb(228, 115, 32)",
opacity,
WebkitMaskImage: `radial-gradient(30% 30px at ${position.x}px ${position.y}px, black 45%, transparent)`,
}}
aria-hidden="true"
className="overlay-input"
/>
</div>
);
};
.relative {
position: relative;
}
.input-wrapper {
width: 20rem;
}
.base-input {
height: 3rem;
width: 100%;
cursor: default;
border-radius: 0.5rem;
border: 1px solid #2d3748;
background-color: #1a202c;
padding: 0.875rem;
color: #cbd5e0;
transition: border-color 0.5s;
outline: none;
}
.base-input:focus {
border-color: #e47320;
}
.base-input::placeholder {
color: #718096;
user-select: none;
}
.overlay-input {
pointer-events: none;
position: absolute;
left: 0;
top: 0;
z-index: 10;
height: 3rem;
width: 100%;
cursor: default;
border-radius: 0.5rem;
background-color: transparent;
padding: 0.875rem;
opacity: 0;
transition: opacity 0.5s;
}
More examples of spotlight borders
You can use this techniques on any element, not just inputs. For example on a card:
I saw a lot of spotlight effect recently, but never saw it on border specificaly. I think it's a really cool way to make a form (or anything else) more interactive. Let me know what you think!
Top comments (0)