DEV Community

Cover image for Creating an interactive spotlight border with CSS and React
Ibelick
Ibelick

Posted on • Originally published at julienthibeaut.xyz

Creating an interactive spotlight border with CSS and React

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

input border spotlight

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>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode
.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;
}
Enter fullscreen mode Exit fullscreen mode

More examples of spotlight borders

You can use this techniques on any element, not just inputs. For example on a card:

card spotlight border

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)