Weight field
A heading whose letters grow bolder under the pointer.
Documentation
-
Copy to Webflow
Add Instrument Sans and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialNothing to size: a letter grows into the space around it and no line moves. Leave the heading withoutoverflow: hidden, which would clip the thick outline, and withoutbackground-clip: text, which every letter would lose; the colour under the pointer is thetintsetting. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Use it on your own elements
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-weight-field Marks the heading whose letters grow bolder: it goes on the heading itself, or on any text element, and takes no value: leave the value field empty. The script splits its text into letters when the page loads and the fonts are in, and puts the heading back as it was with destroy(); in the Designer the heading stays plain text, editable. data-weight-field-area On a parent of the heading, the section of the preview for one: the pointer moving anywhere inside it drives the letters, so the heading answers as the pointer approaches and not only once it is on the text. It takes no value. Without it, only the heading's own box listens. [ Optional ] data-weight-field-state Written by the engine on the heading: ready once the letters are built, static under reduced motion. Never written by hand. data-weight-field-mode, stroke or weight, is written beside it and is what the stylesheet reads to apply the outline. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-weight-field-mode stroke How a letter grows bolder. Options: stroke (the outline thickens in the letter's own ink, with any typeface, and no letter moves), weight (the real weight of a variable font moves between minWeight and maxWeight, each letter held in a cell of its resting width; a font of fixed cuts jumps from cut to cut in this mode). data-weight-field-stroke 0.06 Thickness added to the outline of the letter right under the pointer, in em of the heading, stroke mode; the letters around it take a share of it. data-weight-field-min-weight 400 Weight of a letter far from the pointer, weight mode, on the font's wght axis. data-weight-field-max-weight 700 Weight of the letter right under the pointer, weight mode. data-weight-field-radius 3.4 Reach of the pointer, in em of the heading: how far from the pointer a letter still answers. data-weight-field-follow 0.35 Time a letter takes to reach its thickness once the pointer has moved, in seconds. data-weight-field-tint inherit Ink of the letter under the pointer, mixed into the heading's colour as the letter grows. Options: inherit (the heading's own colour, the letter only grows), or any CSS colour. data-weight-field-idle wave What the heading does with no pointer on it: on a phone, before the mouse has come in, and a moment after it has left. Options: wave (a slow pass sweeps the heading from left to right, again and again), none (the heading rests at its resting thickness until a pointer comes). data-weight-field-idle-period 4 Time the idle pass takes to cross the heading, in seconds.
Two files, no build step.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
WeightField.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-weight-field-mode stroke How a letter grows bolder. Options: stroke (the outline thickens in the letter's own ink, with any typeface, and no letter moves), weight (the real weight of a variable font moves between minWeight and maxWeight, each letter held in a cell of its resting width; a font of fixed cuts jumps from cut to cut in this mode). data-weight-field-stroke 0.06 Thickness added to the outline of the letter right under the pointer, in em of the heading, stroke mode; the letters around it take a share of it. At 0 nothing grows, only the tint moves A heavy outline; past about 0.12 the counters of small letters close and the edges catch data-weight-field-min-weight 400 Weight of a letter far from the pointer, weight mode, on the font's wght axis. A thin resting line A heading already bold at rest, with less room to grow data-weight-field-max-weight 700 Weight of the letter right under the pointer, weight mode. Closer to minWeight, a quiet swell The whole axis of the font, a heavy pull data-weight-field-radius 3.4 Reach of the pointer, in em of the heading: how far from the pointer a letter still answers. Only the letter under the pointer, a spotlight Half the line follows the pointer; the idle pass widens with it data-weight-field-follow 0.35 Time a letter takes to reach its thickness once the pointer has moved, in seconds. A snap, the letters flick as the pointer passes A slow swell that trails the pointer and lingers after it data-weight-field-tint inherit Ink of the letter under the pointer, mixed into the heading's colour as the letter grows. Options: inherit (the heading's own colour, the letter only grows), or any CSS colour. data-weight-field-idle wave What the heading does with no pointer on it: on a phone, before the mouse has come in, and a moment after it has left. Options: wave (a slow pass sweeps the heading from left to right, again and again), none (the heading rests at its resting thickness until a pointer comes). data-weight-field-idle-period 4 Time the idle pass takes to cross the heading, in seconds. A quick sweep, restless under 2 A slow breath across the line -
Drive it from JavaScript
When a heading arrives after the page has loaded, when the page holds the letters still for a moment, or when it takes the effect off.const element = document.querySelector('[data-weight-field]'); const field = WeightField.mount(element); // the heading auto() mounted, or a new one field.freeze(); // the letters hold their thickness, no frame runs field.unfreeze(); // they settle again field.state(); // 'idle', 'pointer' or 'frozen' // A heading added after the page loaded: mount everything marked inside it. WeightField.auto(document.body); // When the heading leaves the page: field.destroy(); // the heading goes back exactly as it was
-
Fit it to your page
- One attribute, on the heading itself. Put
data-weight-fieldon the heading, or on any text element; it takes no value. The engine splits its text into letters when the page loads and the fonts are in, and puts the original markup back withdestroy(). In Webflow, select the Heading, open the Settings panel and add the custom attribute with an empty value. - The pointer may count before it reaches the heading. Put
data-weight-field-areaon a parent, the section of the preview for one: the pointer moving anywhere inside it drives the letters, so the heading answers as the pointer approaches and not only once it is on the text. Without it, only the heading's own box listens. - One setting for every heading of the page, without opening the file: declare
window.WeightFieldSettings = { tint: '#ff4a00' }in a script placed beforeweight-field.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-weight-field-*attribute on the heading itself (data-weight-field-radius="3"), so one heading can still differ from the page-wide setting. - Any typeface, in stroke mode. The outline of a letter is drawn in the letter's own colour, so a family of fixed cuts grows as smoothly as a variable font, and the letter keeps its width. The weight mode wants a variable font with a
wghtaxis that coversminWeighttomaxWeight, and the engine then sets the heading's restingfont-weighttominWeightitself. - Nothing moves but the thickness. The heading's text is laid out once at rest, each line is frozen where it fell and never wraps again, and a letter grows into the space around it. A new width of the heading lays the lines out again, at rest, then measures the letters afresh.
- Touch, and no mouse. A finger drives nothing: on a phone, and on a desktop before the mouse has come in, the idle pass sweeps the heading on its own when
idleiswave. Once a mouse has left the heading, the pass comes back after a short rest. Withidleset tononethe heading simply rests. - **
prefers-reduced-motion**: the heading stands still, at its resting thickness and colour, and no frame runs. - Off screen, nothing runs. The frame loop stops when the heading leaves the viewport and when every letter has settled; it starts again on the next pointer move.
- Without the script, and in the Webflow Designer, the heading reads as plain text, as written, and stays editable. The canvas runs no Embed.
- For a screen reader the heading carries its text as an
aria-label, and the lines, words and letters the engine builds are hidden from it, so the heading reads as one sentence, never letter by letter. - States, on the heading, for a style or a test:
data-weight-field-stateisreadyonce the letters are built,staticunder reduced motion. The mode in force is written indata-weight-field-mode,strokeorweight, which is what the stylesheet reads to apply the outline. - From JavaScript:
WeightField.mount(el, overrides)returns the instance, which carrieselement,settings(the values it resolved),state()(idle,pointerorfrozen),freeze()(the letters hold their thickness and no frame runs),unfreeze()anddestroy().WeightField.auto(root)mounts every marked heading insideroot;WeightField.destroy(el),WeightField.freeze(el)andWeightField.unfreeze(el)do the same by element.destroy()puts the original markup back, so mounting again starts clean. In React,mountinuseEffectanddestroyin its cleanup. - The heading of the markup is the preview's, and it ships as it is: pasted, the page reads the way the preview did. Put the user's own heading in its place whenever they want, in the page's language.
- On Webflow, add the fonts first. Add Instrument Sans and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: the paste names them, and the Designer drops a font the site does not have.
- One attribute, on the heading itself. Put
-
Avoid the pitfalls
- The heading is plain text. Markup inside it, a bold, a link, a line break, is read as its text only: the engine rebuilds the heading out of letters. A heading that needs a link or a second colour inside it is not for this effect; a
<br>is lost, write two headings instead. - **A heading painted through
background-clip: textloses its paint**, since every letter becomes a box of its own. Usetintfor the colour. - **
overflow: hiddenon the heading clips the outline.** The stroke of a heavy letter runs past the letter's box by half its thickness; so does a bolder glyph in weight mode. Leave the heading and its line visible. - The lines are frozen at the resting layout. Each line is laid out once and never wraps again while it plays, which is what keeps the text still. In weight mode a line grows to the right by the width its bold letters add, so a line that fills its box at rest runs a little past it under the pointer; in stroke mode it does not grow at all.
- Letters are split one by one, so the pairs the font kerns and its ligatures are set apart, at rest as in flight. Scripts that join their letters, Arabic for one, are not covered.
- A heading changed after it mounted is not read again. Change the text, then
destroy()andmount(). Settings handed tomount()on a heading already mounted are dropped: it hands the existing instance back. - **The tint needs
color-mix.** The letter's colour is mixed from the heading's ink andtintwith CSScolor-mix, in every current browser; an old browser without it keeps the heading's colour and the letters still grow. - The heading of the preview is set thin, 100. The library's own face has a thin cut, so the resting letters are hairlines and the heavy ones stand out. Instrument Sans, the face that ships, has no cut under 400: the browser draws the heading at 400 and the outline still grows. For the contrast of the preview on your own page, give the heading a face with a light weight, or a lighter
font-weightof yours. - **
-webkit-text-strokeis what thickens the outline**, in every browser under that one prefixed name, Chrome, Safari and Firefox alike. A stroke is centred on the edge of the glyph, so half of it falls inside the letter: at the defaultstrokethe counters of small letters stay open, and a value past about 0.12 closes them.
- The heading is plain text. Markup inside it, a bold, a link, a line break, is read as its text only: the engine rebuilds the heading out of letters. A heading that needs a link or a second colour inside it is not for this effect; a
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Weight field is a text animation for Webflow and vanilla JavaScript, with no library. Also called font weight hover effect, variable font hover effect or proximity text effect.
A heading whose letters grow bolder under the pointer. Each letter answers the distance to the pointer: the closest ones are heavy, the ones at the edge of its reach barely touched, and every letter eases to its thickness over a short moment, so a pass of the mouse reads as a weight moving through the line rather than a switch. The bolder letter takes a tint on the way, the heading's own ink or a colour of your own. What grows is the outline of each letter, drawn in its own ink, so any typeface works, a variable one or a family of fixed cuts, and no letter and no line moves while it plays: the words are kept whole, the lines frozen where they fell at rest. The reach around the pointer is a flat ellipse, so the line under the pointer reacts and the lines above and below stay at rest. Without a pointer, on a phone or before the mouse has come in, a slow pass sweeps the heading from left to right, so the effect is seen without anyone touching it. Under reduced motion the heading stands still. A second mode moves the real weight of a variable font instead of the outline, each letter kept in a cell of its resting width. Pattern type variable font hover, font weight on hover, proximity text effect.
Updated
Questions
Select the Heading, open the Settings panel and add the custom attribute data-weight-field with an empty value. The script splits the heading into letters once the fonts are in, and each letter thickens as the pointer comes near. To have the letters answer before the pointer reaches the text, add data-weight-field-area, empty too, on the section around the heading. In the Designer the heading stays plain and editable, since the canvas runs no Embed.
No. By default what grows is the outline of each letter, drawn in its own ink with -webkit-text-stroke, so a family of fixed cuts thickens as smoothly as a variable font and no letter changes width. A variable font with a wght axis can move its real weight instead: set data-weight-field-mode="weight", and data-weight-field-min-weight and data-weight-field-max-weight give the two ends of the axis.
Set data-weight-field-tint on the heading to any CSS colour, #ff4a00 in the preview. The letter's colour is mixed from the heading's ink and that tint as the letter grows, so the ones at the edge of the reach barely change. inherit, the default, keeps the heading's own colour and the letters only grow.
data-weight-field-radius is the reach in em of the heading, 3.4 by default: lower it to a spotlight on one letter, raise it to have half the line follow the pointer. data-weight-field-follow, 0.35 by default, is the time in seconds a letter takes to reach its thickness; a lower value snaps, a higher one trails the pointer and lingers after it.
A slow pass sweeps the heading from left to right, again and again, every 4 seconds by default (data-weight-field-idle-period), so the effect is seen without anyone touching it. The same pass runs on a desktop before the mouse has come in and a moment after it has left. data-weight-field-idle="none" turns it off, and under reduced motion the heading stands still.