081

Pinboard Notes

£5

Notes and prints pinned to a cork board. Drag one and the paper hangs from its pin, swings with your movement, then presses back into the board on release. Tie notes together with string that sags and sways, and write a new note that finds its own place.

intermediate
6 more details
pointer-dragspring-physicsdynamic-contentrubber-bandmomentumsvg-path
Pinboard Notes - GSAP animation effect preview

This demo reads better at your own screen size than in the frame below:

About this effect

A studio pinboard of prints, an index card and sticky notes, each held by one pin. Nothing here behaves like a draggable panel: dragging leads with the pin and lets the paper trail behind it, so a fast pull makes the note lean, a sudden stop makes it wobble, and letting go swings it back to the angle it was pinned at.

Read the full effect overview

The pin comes out of the board while you hold a note, its cast shadow moves further away and the paper grows slightly towards you. Drop it and the pin presses back in with an elastic settle. Pull a note past the board edge and it gives only a fraction of the distance, then springs back inside.

Notes can be tied together with string, pin to pin. Each string hangs with a sag worked out from its own length, trails and sways when the notes it joins move, and settles on the same spring as the paper. Declare them in markup or tie them from code.

The board is a live list. Append a note and it is wired up on the spot, with the same arrival the shipped notes get; mark it for automatic placement and it finds its own free slot, keeping clear of the notes already pinned and of anything the page reserves. Drop a note on a marked zone to unpin it. Every pin and unpin is announced, so a page can keep a count or a store in step.

What's included

20 items
  • Paper physics, not panel dragging: a damped spring makes the note trail the pin, lean into fast movement and wobble when you stop
  • The pin is the pivot: each note turns about its own pin head, so the swing is anchored rather than a rotation about the box centre
  • Visible lift state while you hold a note: the pin rises out of the board, the cast shadow softens and the paper scales slightly towards the camera
  • Elastic press-in on release, and a fling that carries your release velocity into the swing instead of stopping dead
  • Soft board edges: a note dragged past them follows only a fraction of the extra distance and springs back inside
  • A tap that never travelled gives the note a nudge, so touch visitors meet the physics without discovering drag first
  • Live board: append a note element and it becomes draggable on the spot, remove one and it is torn down, with no re-initialise
  • Automatic placement: a note marked data-pin-note-place="auto" is given the first free slot, scored to avoid covering anyone's writing, or the least crowded slot when the board is full
  • Reserved space: mark a bar, a logo or a toolbar inside the board with data-pinboard-keep-clear and new notes stay off it
  • Unpin three ways: drop a note on a data-pinboard-remove zone, press Delete on a focused note, or take the element out of the page
  • pinboard:change fires on every pin and unpin, whether the effect or the page made it, so a count, a save routine or a disabled button stays in step
  • A small programmatic API: add a note from markup, unpin one with the same exit, or reach any board on the page
  • Tie notes together with string: each string sags from its own length, trails and sways when its notes move, and settles on the paper's spring model. Declare strings in markup or tie them from code
  • Keyboard operable: arrow keys move a focused note with the same swing response, Shift for larger steps, Home to pin it back, Delete to unpin it
  • One re-pin control returns the whole board to its authored arrangement with a staggered wave of swings
  • Positions, sizes, tilts and paper colours live in CSS, so the board scales as one object and the script never needs a class name
  • Ships two arrangements, wide (16:9) and tall (1:2), switched by the board's own shape
  • Cork surface, slim frame, washi tape and three pin finishes in the demo, all of it replaceable CSS
  • Ticker only runs while a note is moving, and is removed on teardown along with every listener
  • Reduced-motion and no-JavaScript visitors get the complete board as plain, readable, static paper

Perfect for

5 use cases
  • A task or notice board where notes can be written, moved and taken down without a page reload
  • An agency or studio process page where the working notes are the content
  • A testimonial or feedback wall a visitor can add to and rearrange
  • Product roadmaps, launch boards and moodboards that should feel physical rather than like a kanban app
  • Editorial and magazine features built around pinned evidence, prints or cuttings

How it works

5 sections

Each note is positioned by CSS and carries a resting tilt in a `--tilt` custom property. On init the script reads the note's box and its holder, pivots the note on the holder, and hands the resting tilt to GSAP, so the swing is anchored at the pin rather than at the element's centre.

One shared clock integrates a damped spring for every note on the board: pointer speed becomes a swing target while you drag, and zero once you let go, so the same equation covers leaning, wobbling and settling. Notes at rest below a small threshold are skipped, and the clock is removed when the whole board is still.

Drag distance is applied as one transform per note, clamped against the board with a rubber band that yields less the further you pull, while the pin and the paper's shadow change through a lifted state.

Strings are quadratic curves in one SVG layer over the board. Each string's sag comes from its length and the gap between its pins, and two more springs on the same clock let it lag behind and sway when its ends move.

The pin surface is watched as a live list. A note appended to it is wired up straight away and arrives the same way the shipped notes do; a note removed from it is torn down. A note asking for automatic placement is scored against every other note's footprint and its written area, so a new note prefers the blank margin of a busy board over somebody's text, and against reserved zones it keeps clear of altogether. The board builds twice over: a physics build for a normal motion preference and a plain build that still drags, steps and edits but never swings, plus wide and tall conditions so a change of board shape rebuilds from the authored arrangement.

Difficulty Intermediate
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, 15 September 2026. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Pinboard Notes opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects.

£5 Standard license, unlimited projects

Browse free effects

Documentation

Quick Start

1. Add to your HTML <head>:

Code snippet omitted: it ships with the download.

2. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

3. Add a board anywhere in your <body>:

Code snippet omitted: it ships with the download.

4. Optional: a re-pin control, anywhere on the page.

Code snippet omitted: it ships with the download.

The value is the board's id, so a page can hold more than one board and address each one separately. The button returns every note to the position CSS authored for it, with a staggered wave of swings as they arrive.

Using It With Your Own Design

What the effect needs from your markup: a handful of data- hooks, three of them required (data-pinboard, data-pinboard-face, data-pin-note).

Hook On Meaning
data-pinboard the board root One board. The script finds the face and the notes inside it and scopes everything to that board, so several boards can share a page.
data-pinboard-face the pin surface The box a note is kept inside, and the container its cqw units resolve against. Must be position: relative.
data-pin-note each note Anything with this attribute is draggable, focusable and measured. Children split into .pin-note__paper (the sheet that is tilted and scaled) and whatever holds it: .pin-note__pin or .pin-note__tape.
data-pin-note-place="auto" a note Put this note wherever there is room, instead of where CSS put it. For notes a page or a visitor adds at runtime.
data-pinboard-keep-clear any element in the face Reserved space. Automatic placement keeps new notes off it.
data-pinboard-remove any element in the face A drop zone. Release a held note over it and the note is unpinned.
data-pinboard-reset a <button> Optional. Its value is the id of the board it re-pins: every note goes home, unpinned notes from the markup come back, and the strings are the ones the markup declares.
data-pin-note-id / data-pin-note-links notes Optional. Tie strings in markup: give a note an id, and list ids on another (data-pin-note-links="kiln glaze").

Inside a note, only two elements are read by the script: the sheet, .pin-note__paper, and whatever holds it, .pin-note__pin or .pin-note__tape. Everything else inside the paper is yours — headings, lists, figures, photos, a canvas, whatever the note is for.

Where a note sits, how big it is and how far it is tilted all come from CSS. The script reads the note's layout box (so any unit works: %, px, cqw, ch), reads the resting angle from a --tilt custom property, and derives the pivot from where the pin or tape actually landed. It never writes a position of its own except the drag offset on top of yours. That means you reposition a note by editing one line:

Code snippet omitted: it ships with the download.

The holder is the pivot. Put the pin (or the strip of tape) wherever the note should hang from — the script measures its centre and turns the note about it, so the sheet swings from the pin instead of spinning about its own middle. Move the holder and the physics follows:

Code snippet omitted: it ships with the download.

What is only the demo's styling: the paper palette, the prints, the ruled card, the cork and frame, the note colours, the page ground, and the toolbar with its composer (.composer, plus the demo-only script in index.html that appends notes to the board). Nothing in script.js refers to any of them. Give every note its own --paper and you have a different board:

Code snippet omitted: it ships with the download.

Delete the demo's .note--* rules entirely and author your own; the script only needs the hooks above and the sheet/holder classes.

CSS the effect depends on: three rules, and the first two are load-bearing.

  1. transform-origin on the note must be the holder (50% <pin-y>). The script re-applies it on every measure, but the CSS value is what a no-JavaScript visitor gets.
  2. The .has-js .pin-note { opacity: 0 } pre-hide, with the prefers-reduced-motion restore underneath it. Without the gate, notes paint where they belong and then jump; without the restore, reduced-motion visitors see an empty board. Notes appended later are covered by the same gate, which is what lets them arrive instead of appearing.
  3. touch-action: none and user-select: none on the note, so a touch drag does not pan the page and a drag does not select the note's text. Both are on .pin-note already.

The board's own sizing is a suggestion, not a requirement: the demo keeps a 16:9 board on a landscape viewport and a 1:2 one in portrait, and sizes everything inside in cqw so the composition scales as one object. A fixed-height board works just as well. Notes are as tall as their content, so longer copy makes a taller note rather than an overflowing one — but the two shipped arrangements assume roughly the copy lengths in index.html.

Options

Attribute / property Values Default Description
data-pin-note Marks an element as a draggable note.
data-pin-note-place auto Place this note in the first free slot instead of at its CSS position.
data-pinboard Marks a board root; one instance per board.
data-pinboard-face The pin surface. Notes are clamped inside it and cqw resolves against it.
data-pinboard-keep-clear Automatic placement treats this element as occupied (a footer bar, a logo, a toolbar).
data-pinboard-remove Drop zone: a note released over it comes off the board.
data-pinboard-reset="<id>" a board id Button that re-pins every note on that board and restores its authored notes and strings.
data-pin-note-id any id A name other notes can tie a string to.
data-pin-note-links space-separated ids Strings tied from this note to the notes with those ids when the board builds.
--string any colour #d62a1f String colour (demo stylesheet).
--tilt any angle 0deg The note's resting angle. The swing springs back to this, not to zero.
--w any length 16cqw Note width.
--pin-y any length 1.4cqw Distance from the note's top edge to the pin centre, which is the pivot.
--pin-size any length 1.9cqw Pin diameter.
--tape-y / --tape-w / --tape-h any lengths -1.4cqw / 36% / 3.2cqw Tape strip position, width and height, when a note is taped rather than pinned.
--paper any colour var(--surface) Paper colour, set on the note and read by .pin-note__paper.
--paper-pad padding 3.2cqw 1.7cqw 1.7cqw Paper padding. Keep the top large enough to clear the pin.
--paper-type any length 1.5cqw Base type size inside the paper; the demo's content sizes in em from here.
.is-dragging class Set on a note while the pointer holds it.
.is-lifted class Set while the pin is out of the board, so the shadow can change.
.is-over-drop class Set on a held note while it is over a drop zone.
.is-target class Set on the drop zone the held note is over.

Adding and Removing Notes

The board watches its own surface. Append a [data-pin-note] element to the face and it is wired up on the spot: measured, made draggable and focusable, and settled in with the same arrival the shipped notes get. Remove it and it is torn down, listeners and all. Nothing to call and nothing to re-initialise:

Code snippet omitted: it ships with the download.

A note added this way needs no position: with data-pin-note-place="auto" the script scans the board for room, treats every other note and anything marked data-pinboard-keep-clear as occupied, and picks a free slot — or the least crowded one when the board really is full.

Two smaller pieces, both optional:

  • Take notes off by dropping a held note onto any data-pinboard-remove zone, by pressing Delete or Backspace on a focused note, or by removing the element yourself.
  • Know when it happens by listening for pinboard:change on the board. It fires on every pin and unpin, whether the effect did it or the page did, which is what a save routine, a count or a disabled button wants.

Automatic placement needs JavaScript, so give a note that relies on it a sensible fallback position in CSS if the board has to look right without scripting. Notes that are added by a script only exist when scripting runs, so this only matters for boards rendered from data.

Tying Notes with String

Strings join notes pin to pin; a taped note takes no string. Each one hangs with a sag worked out from its own length and the gap between its ends, so pulling two notes apart draws the string taut and pushing them together lets it droop. Move a note and its strings trail behind it, sway, and settle on the same spring model as the paper.

  • In markup: data-pin-note-id="kiln" on one note, data-pin-note-links="kiln" on another. Lengths come from the authored positions, with a little slack.
  • Visitors move the notes, not the strings: the demo ships fixed strings. If your board should let people tie their own, call link() from whatever gesture suits it.
  • From code: window.pinboardNotes.link(a, b) and .unlink(a, b).

Unpinning a note cuts its strings. Every tie and cut fires pinboard:change with type: 'link' or 'unlink' and both notes in the detail.

The script draws strings into its own <svg class="pinboard__strings"> layer inside the face. Their look is the .pinboard__string and .pinboard__string-shadow rules in the stylesheet, so a colour, weight or dashed thread is a CSS change. The string is trimmed at each pin by the pin's radius, so it reads as tied under the head.

Accessibility

  • Keyboard: every note is focusable (the script adds tabindex="0" only when the script runs, so a no-JavaScript page has no dead tab stops). Arrow keys move the focused note with the same swing response as a drag, Shift takes larger steps, Home pins it back where it started, and Delete or Backspace unpins it. Strings are decorative and hidden from assistive technology; a tie or cut made from code is announced through a polite status region. Arrow keys and Delete are preventDefaulted, so they act on the note rather than the page.
  • Touch: pointer events with capture, so a drag keeps working when the pointer leaves the note. touch-action: none stops the drag turning into a page pan.
  • Reduced motion: the board builds without physics. Notes still drag, still move with the arrow keys and still pin and unpin — moving a note is the whole point of the board — but nothing swings, settles, lifts or scales on its own, and nothing falls into place.
  • No JavaScript or a blocked CDN: the script removes .has-js when GSAP is missing, which restores the pre-hidden notes. The board is then complete, static paper: every note readable, in the position and at the angle CSS authored.
  • Screen readers: notes are real elements with real copy. The script adds a hidden instruction (aria-describedby) explaining dragging, the arrow keys, Home and Delete to a note when it is focused; that attribute and the helper element are both removed on teardown.
  • Contrast: paper colours are yours, so check text against the paper you choose. The demo's notes are all well past 4.5:1.

Dependencies

Required:

  • GSAP 3.12+ (core only — no plugins)

Optional:

  • None. This effect ships with no plugins, no canvas and no external library.

The demo pins GSAP 3.15.0; 3.12+ is the compatibility floor.

Credits

Photographs bundled with the demo are from Pexels under the Pexels License: rows of unfired bowls, bowls packed in an open kiln, and hands throwing on a wheel. They are baked into assets/img/ as WebP derivatives; the source URLs and the grade applied are recorded in assets/img-manifest.json, and bun scripts/build-template-assets.ts pinboard-notes regenerates them. Replace the files, or edit the manifest and re-bake, to use your own photography.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.

Your cart

Your cart is empty

The Vault £99

The Vault library, plus future additions to the library.