How We Built This Component

We built this like a layered design system. Each layer does one simple job, and together they create the final shimmer look.

1) We created the base chat container

First, we made the regular chat box shape. This is the stable layout that holds textarea, chip, and send button. Think of this as the "canvas" for all animation layers.

.chat-wrap {
  position: relative;
  width: 450px;
  height: 120px;
  border-radius: 12px;
  overflow: hidden;
}

2) We added an animated gradient border ring

The colorful border is a rotating conic gradient. We use a mask so only the ring is visible, not the full box fill.

@keyframes shimmer-spin {
  to { --shimmer-angle: 360deg; }
}

.gradient-ring {
  background: conic-gradient(from var(--shimmer-angle), ...);
  animation: shimmer-spin 4s linear infinite;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
}

3) We added inner shimmer as a separate layer

To avoid breaking border animation, inner shimmer is isolated in .chat-inner::before. It has its own animation angle and moving mask, so both effects can run independently.

@property --inner-shimmer-angle {
  syntax: '<angle>';
  initial-value: 0deg;
}

.chat-inner::before {
  background: radial-gradient(...), radial-gradient(...);
  -webkit-mask-image: conic-gradient(from var(--inner-shimmer-angle), ...);
  animation: inner-shimmer-spin 3.2s linear infinite;
}

4) We controlled visibility with state classes

JavaScript adds and removes classes based on focus/input/blur. This turns shimmer on while interacting, then softly cools it down instead of instantly removing it.

textarea.addEventListener('focus', activateBeam);
textarea.addEventListener('input', activateBeam);
textarea.addEventListener('blur', () => {
  setTimeout(() => fadeBeam(), 700);
});

5) Final principle we followed

Do not couple animations. Border and inner shimmer use separate variables and layers. That keeps the component stable, easy to tune, and easier to debug.

 1<div class="chat-outer">
 2
 3  <!-- Sharp gradient border box -->
 4  <div class="chat-box">
 5    <div class="chat-inner">
 6
 7      <textarea
 8        class="chat-textarea"
 9        placeholder="Type your message..."
10        rows="2"
11      ></textarea>
12
13      <div class="chat-row">
14        <div class="chat-chip">
15          <span class="chat-chip__label">Research mode</span>
16          <span class="chat-chip__close">
17            <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
18              <path d="M2 2L8 8M8 2L2 8"
19                stroke="currentColor" stroke-width="1.4"
20                stroke-linecap="round"/>
21            </svg>
22          </span>
23        </div>
24        <button class="chat-send">
25          <div class="chat-send__icon"></div>
26        </button>
27      </div>
28
29    </div>
30  </div>
31
32</div>
 1@property --shimmer-angle {
 2  syntax: '<angle>';
 3  initial-value: 0deg;
 4  inherits: false;
 5}
 6
 7@keyframes shimmer-spin {
 8  to { --shimmer-angle: 360deg; }
 9}
10
11/* Outer wrapper: stacking context for glow */
12.chat-outer {
13  position: relative;
14  width: 450px;
15  border-radius: 14px;
16  isolation: isolate;
17}
18
19/* Layer 1: blurred glow behind the box */
20.chat-outer::before {
21  content: '';
22  position: absolute;
23  inset: -5px;
24  border-radius: 17px;
25  background: conic-gradient(
26    from var(--shimmer-angle),
27    #4F8EFF, #A855F7, #F472B6, #FB7185, #4F8EFF
28  );
29  filter: blur(14px);
30  opacity: 0;
31  z-index: -1;
32  animation: shimmer-spin 3s linear infinite;
33  transition: opacity 0.4s ease;
34}
35
36/* Layer 2: box with sharp gradient border */
37.chat-box {
38  position: relative;
39  z-index: 1;
40  border-radius: 12px;
41  border: 2px solid transparent;
42  background-image:
43    linear-gradient(#fff, #fff),
44    linear-gradient(#EDEDED, #EDEDED);
45  background-origin: padding-box, border-box;
46  background-clip: padding-box, border-box;
47}
48
49/* Active: shimmer ON */
50.chat-outer.is-active::before {
51  opacity: 0.65;
52}
53
54.chat-outer.is-active .chat-box {
55  background-image:
56    linear-gradient(#fff, #fff),
57    conic-gradient(
58      from var(--shimmer-angle),
59      #4F8EFF, #A855F7, #F472B6, #FB7185, #4F8EFF
60    );
61  background-origin: padding-box, border-box;
62  background-clip: padding-box, border-box;
63  animation: shimmer-spin 3s linear infinite;
64}
1const chatOuter = document.querySelector('.chat-outer');
2const textarea  = document.querySelector('.chat-textarea');
3
4textarea.addEventListener('focus', () => {
5  chatOuter.classList.add('is-active');
6});
7
8textarea.addEventListener('blur', () => {
9  chatOuter.classList.remove('is-active');
10});
Preview Click the input to activate shimmer
Research mode