Skip to content
animorio
IT
All components

The avatar to assign

Avatars, Physics, HoverGSAPAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

In the minimal style: who a task belongs to. An empty seat drawn by a dashed circle and, under a hairline, the team in a row of initials. The face chosen with a click, a tap or Enter rises out of the row, flies into the seat filling with ink, lands with a small squash and the blue ring draws round it from the side it came from, while the name beside it takes weight letter by letter. It can also be dragged there by hand: near the seat a magnet pulls it in. The row closes the gap one face after another.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The avatar to assign</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- who a task belongs to: an empty seat drawn with a dashed hairline, and under the rule the team in a row of
         initials. A face chosen (a click, a tap, Enter, or dragged by hand) lifts out of the row, flies on an arc into
         the seat filling with ink, lands with a small squash and the seat's ring draws round it in the accent; the name
         beside the seat takes weight letter by letter. The faces are made by the script from PEOPLE -->
    <div class="assign" role="group" aria-labelledby="assign-label" data-assign data-autoclic>
      <div class="seat" aria-hidden="true" data-seat>
        <svg class="seat__ring" viewBox="0 0 76 76" data-ring>
          <circle class="seat__dash" cx="38" cy="38" r="36" data-dash/>
          <circle class="seat__solid" cx="38" cy="38" r="36" data-solid/>
        </svg>
        <svg class="seat__plus" viewBox="0 0 16 16" data-plus><path d="M8 2.5v11M2.5 8h11"/></svg>
      </div>

      <div class="who">
        <span class="who__label" id="assign-label" data-label>Assignee</span>
        <span class="who__name" aria-hidden="true" data-name></span>
        <span class="who__role" aria-hidden="true" data-role></span>
      </div>

      <span class="assign__rule" aria-hidden="true" data-rule></span>
      <div class="team" role="group" aria-label="Team" data-team></div>
      <p class="sr" aria-live="polite" data-live></p>
    </div>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

31 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The avatar to assign” from Animorio and fit it into my site

What it does

On a white page, on the left, an empty seat: a thin grey dashed circle with a small plus in the middle. Beside it the word Assignee, small and grey, and under it Unassigned, large and light. Under a very thin rule is the team: five black hairline circles with the initials MO, TL, IP, RS and JA. On opening the rule draws from the left, the dashed circle turns into place and the faces rise one after another and settle. With the pointer over a face, the face rises three pixels, its line grows thicker and beside the seat its name appears, pale and light, like a suggestion. On a click the name steps aside and the face rises straight out of the row, then glides into the seat growing to its size, leaning a little into the flight with a soft shadow under it: on the way the ink spreads from the centre of the circle and the initials turn white, fuller and narrower, like a monogram. It lands with a small squash that bounces back, the dashes go and a blue ring draws round the face, starting from the side it came from and closing on the other. The name comes in letter by letter opening out in width, takes its weight from left to right with a little knock, and under it the role appears, Design lead. Meanwhile the row closes the gap: the face next to it first, then the others. Choosing another face, the one in the seat goes home first, gliding out and dropping into the row that opens its place, and the new one leaves as soon as the other is on its way down, so the two never cross. Clicking the face in the seat takes it out: the ring draws back, the ink closes into the centre and the face goes back into the row, and its neighbours feel it arrive. Every face can also be dragged: it lifts, leans with the movement and, near the seat, a magnet pulls it in, the dashed circle opens a little and turns black and the name appears beside it; let go there, it goes into the seat, let go far away it goes back into the row. Dragging the face in the seat out of it takes it out.

How it works

Measures in pixels and springs made by hand

The whole component is a box in pixels worked out by the script from ROW_SIZE, SEAT_SIZE and GAP: the seat at the top left, the name beside it, the rule and the row underneath. Every face is an absolutely positioned button, the size of the seat and shrunk in the row with its scale (ROW_SIZE / SEAT_SIZE), so in the seat the initials are crisp. x, y, scale and rotation reach the face through quickSetter, from damped springs integrated by hand on the GSAP ticker (acceleration −k·distance − 2ζ√k·speed) with the step capped at 1/30 of a second and split into steps of 1/240. When everything is still the loop takes itself off the ticker. On resize the measures are worked out again and the springs carry the faces to their new places.

A flight that passes over nothing

The flight into the seat is a quadratic Bézier curve run by a GSAP tween: the control point sits between the midpoint and the corner (above the face, at the height of the seat), by as much as ARC says. With ARC near 1 the face rises straight out of the row, without passing over its neighbours, and then glides into the seat; going home it does the opposite, slides out sideways and drops into its place. The scale grows along the way with a little swell in the middle (a sine), the face leans a few degrees into the flight and a soft shadow comes and goes with keyframes. During the flight the name beside the seat steps aside, because the flight passes right where it is written: two things are never on top of each other.

The ink, and initials drawn twice

The black ground of the chosen face is a circle with clip-path: circle() that GSAP takes from 0 to 51 per cent, from the centre out. The initials are written twice: in black on the white face and in white inside the ink, cut by the same circle; so at every moment every part of a letter reads, even while the edge of the ink cuts through it. The weight and width of the initials (the wght and wdth axes of Bricolage Grotesque, in CSS variables on the button) change during the flight: in the seat they are fuller and narrower, like a monogram. On arrival a small squash (SQUASH) springs back with an elastic ease.

Dragging, and the magnet

With Pointer Events a face is dragged after 6 pixels of movement (before that it is a click); touch-action: none on the faces keeps the screen still under the finger. The face follows the pointer on a stiffer spring, grows a little and leans with its speed. Near the seat, within MAGNET pixels, the point it is heading for is pulled towards the centre of the seat with a weight of (1 − d / MAGNET)² · 0.6, so the face lets itself be caught. Near enough, the dashed circle opens and turns black and the name appears beside it: let go there it goes into the seat, far away it goes back into the row. The click that follows a drag is ignored.

The name, the ring and the row

The name is made of spans, one per letter, with font-variation-settings written from two CSS variables that GSAP runs: it comes in with wdth from 75 to 100 and a small overshoot, then takes weight from 330 to 640 one letter after another (STAGGER); the old one leaves upwards before the new one comes in. The ring is a single stroke of an SVG circle with stroke-dasharray L, C − L and stroke-dashoffset L / 2, turned towards the point the face arrives from: it grows both ways until it closes. The row takes its new places in turn: closing a gap the neighbours go first, opening one the farthest go first, so nobody bumps into the face beside it. In a swap the old face goes home first and the new one leaves when the other is on its way down.

The preview’s round

A click from a script on the component (detail 0, the library’s preview) plays a whole round on a GSAP timeline: a face looked at and chosen, a second one taking its place, the seat freed, a third face carried by a pretend hand over the row to the magnet, and the seat empty again, as it began. Real clicks and the keyboard stop the round.

Values to change

NameFileDefaultWhat it changes
PEOPLEscript.js[['Maya Ortiz', 'Design lead'], …]The team: each person’s name and role. The initials come from the name.
STARTscript.js-1Who sits in the seat when the page opens: their place in PEOPLE counting from 0, or −1 for nobody.
ROW_SIZEscript.js44How big a face is in the row, in pixels.
SEAT_SIZEscript.js56How big the face in the seat is, in pixels.
GAPscript.js12The space between two faces in the row, in pixels.
FLIGHTscript.js0.64How long the flight to the seat lasts, in seconds (a short flight takes less).
ARCscript.js0.9How square the flight is: towards 1 the face rises straight out of the row and then goes across, lower it cuts diagonally.
SPRINGscript.js[380, 0.7]The faces’ spring: how stiff it is and how much it is held back (1 does not bounce).
MAGNETscript.js86From how many pixels away the seat starts pulling in a dragged face.
SQUASHscript.js0.07How much the face flattens when it lands.
STAGGERscript.js0.024The delay from one letter of the name to the next, in seconds.
EMPTYscript.jsUnassignedThe word beside the empty seat.

Try them in the Playground

When to use it

Wherever a person is chosen for something: who a task belongs to in a project app, who looks after a client or an order, who answers a message in a shared inbox, who is on shift, who reviews a document. It also works as the choice of an owner in a form, with the people a click away instead of a dropdown.

On phones

The component is 300 pixels wide and fits a 390 screen without touching the edges; with more people or bigger faces the row draws closer to fit. Touching a face shows its name beside the seat while the finger is down, like hovering with the pointer; when the finger lifts the face flies into the seat with the same movement as on a computer. It can be dragged with a finger too: the screen does not scroll while you carry it.

With reduced motion

No flights and no springs: the chosen face fades out of the row and reappears in the seat already full of ink, the row rearranges itself at once as soon as the face has faded, the ring fades in instead of drawing and the letters of the name come and go with a fade only, without rising and without changing width. The dashed circle does not open when a dragged face comes near: it only turns black.

Pitfalls

  • In a swap the two faces collided: the new one left from exactly the place in the row where the old one was going back. Now the old one goes home first, the row opens its place (the new one meanwhile waits in the row) and the new one leaves when the other is on its way down.
  • The upward arc of the flight passed over the name written beside the seat, and the direct one over the faces in the row. Now the face rises straight up and then goes across, and the name steps aside during the flight.
  • The ink growing with scale while the initials changed colour left black letters on black halfway. Now the initials are written twice, black underneath and white inside the ink, cut by the same clip-path.
  • Opening a gap, the faces next to it moved first and ran into the ones further along: now opening, the farthest go first, and closing, the nearest. With reduced motion too the row rearranged itself under the face that was fading: now it waits until it has gone.
  • After a click the face sliding under the still pointer took the hover and its pale name covered the one just chosen: now no name is suggested until the pointer moves.
  • gsap.quickSetter with 'scale' threw «scaleX,scaleY is not a valid attribute name»: it takes two setters, one for scaleX and one for scaleY.
  • With the keyboard focus on the face in the seat there were two blue rings, the focus one and the seat’s: now the seat’s ring grows thicker.

Libraries

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.

Testing

Tested on 18 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components