Skip to content

No.519 · Micro-animations· New

Echo Bar

The action row under a post — reply, repost or quote, like, views, bookmark, share — with honest counts. Presses apply at once, rapid presses settle into one request, a failure puts the count back and says why, and live counts from the server never double-count your own like. Only the digits that change roll, and 999 becomes 1K without a jump.

The prompt

Copy this text

Automatic copying is unavailable. Copy the selected text using your device’s copy command.

Written for the variant you picked above: Paper

Build the action toolbar under a social post. Props: counts {replies, reposts, likes, views, bookmarks?} and viewer {liked, reposted, bookmarked} as the server last reported them, commit(action, on) returning a promise, onReply, onQuote, onSend, link, analytics rows, replyLimit (a reason that disables Reply), hideCounts, label, scopeRef and motion. Layout: six buttons on one row in fixed slots, each an icon (19px, 1.7 stroke) and a count, 36px tall pill hit areas, colourless until hovered or active. Tones: reply and views and bookmark and share #1d8cf0, repost #11a26a, like #f0386b. Bookmark sits right-aligned before Share. Counts are compact and rounded down so they never overstate: 999, 1.2K (one decimal only below ten of a unit, '.0' dropped), 12K, 1.2M. Each digit is its own 0–9 reel translated to its value (460ms, ease-out), keyed by its position from the right, so 1,204 → 1,205 moves only the last reel and 999 → 1K rolls the digit while the K fades and rises in as a new glyph. Tabular figures throughout. Optimistic toggles (like, repost, bookmark): keep the viewer's intent and what the server confirmed separately. A press flips the intent and the screen at once. If no request is out, send one; presses while it's out only move the intent, and when it lands a second request goes only if the intent still differs — five quick taps cost at most two requests, for the last intent. A rejection puts the intent back to the confirmed value, nudges the button sideways once (WAAPI, so focus is kept), and announces 'Couldn't like: <reason>. Put back as it was.' The shown count is serverCount − (serverSaysYou ? 1 : 0) + (intent ? 1 : 0), so a fresh count from the server, including other people's likes, never double-counts your own. A server change arriving while nothing is pending becomes the new truth. Like fills the heart with a small pop (0.4 → 1.22 → 1, 460ms) and six sparks; unliking is quiet. Repost opens a two-item menu (Repost or Undo repost, and Quote); when reposted the arrows turn 180° and go green. Views is read-only: it opens a small analytics dialog (Impressions, Engagements, Profile visits, Link clicks, full numbers). Share opens a menu: Copy link (says Copied with a tick only after the clipboard write resolves; if it fails, the link appears in a read-only field, selected on focus), Share via… only where the system share sheet exists, and Send in a message. With replyLimit set, Reply is aria-disabled and a tooltip on hover and focus gives the reason ('Only people Omar mentioned can reply'). hideCounts removes like and repost counts but keeps the buttons. Keyboard: role=toolbar with one tab stop and roving focus on ←/→/Home/End. Menus are real menu buttons (aria-haspopup, aria-expanded), focus their first item, move with ↑/↓, close on Escape with focus returned, and on outside press. Shortcuts L (like), R (reply), T (opens the repost menu rather than reposting blind), S (share) and B (bookmark) work only while focus is inside the post or the bar — never page-wide — and never while typing. The bar measures itself: below 340px wide, Views leaves the row and becomes an item in the Share menu; below 220px (a container query) the counts hide visually and stay in each button's label. Selected variant — Paper (paper): Show the posts on a light page: page #f2f1ed, cards white with a 6% black ring and a soft drop shadow, text #16171a, muted text at 55%. Menus use --echo-surface #ffffff with --echo-ink #16171a; the tooltip inverts them. This selection takes precedence over references to other themes or options in the general brief. Implement this selected appearance; do not add a variant picker or alternative appearances unless requested. Implementation requirements: Use React and TypeScript. Keep the component self-contained, with uniquely scoped CSS and no animation libraries. Include required imports, styles and font setup in the result. Interaction: Press any action. Like and bookmark toggle; Repost and Share open menus; Views opens the post's analytics. Tap quickly several times to see the presses settle into one request; tick 'Simulate offline' to see a press roll back with a reason. With the post focused, L, R, T, S and B act on it. Motion: Digit reels 460ms ease-out, new suffix 360ms rise-and-fade; heart pop 460ms with six sparks over 520ms; repost arrows turn 180° over 420ms; menus open in 160ms; a rolled-back press nudges sideways for 320ms. Reduced motion: no pop, sparks, nudge or rolling — counts and colours change in place. Accessibility: A toolbar with roving focus. Like and bookmark are toggle buttons with aria-pressed; repost and share are menu buttons; views opens a labelled dialog. Each label carries its full count ('Like, 1,204 likes'), so counts can hide visually without being lost. Every change and every rollback is announced politely with its reason. Disabled reply keeps focus and explains itself via aria-describedby. Shortcuts are scoped to the focused post. Responsive behavior: The bar fills its container. Below 340px, Views moves into the Share menu; below 220px, counts hide and stay in the labels. Menus open inward from the edge they sit on. Touch: Every action is a tap target of at least 36px; the reply reason shows on focus as well as hover, and there's nothing that needs hover. Preserve keyboard access, visible focus and reduced-motion behavior. Keep fictional demo data and simulated service calls in a separate usage example; expose callbacks for real actions and recover from rejected requests. Keep usage-example links inside the demo: use local buttons or prevent navigation. Demonstration links must not navigate to Vitrine routes or open other pages; preserve real URL support in the reusable component itself. Verify the result at narrow and wide viewport sizes, with keyboard input and reduced motion enabled.

Notes

Interaction
Press any action. Like and bookmark toggle; Repost and Share open menus; Views opens the post's analytics. Tap quickly several times to see the presses settle into one request; tick 'Simulate offline' to see a press roll back with a reason. With the post focused, L, R, T, S and B act on it.
Motion
Digit reels 460ms ease-out, new suffix 360ms rise-and-fade; heart pop 460ms with six sparks over 520ms; repost arrows turn 180° over 420ms; menus open in 160ms; a rolled-back press nudges sideways for 320ms. Reduced motion: no pop, sparks, nudge or rolling — counts and colours change in place.
Accessibility
A toolbar with roving focus. Like and bookmark are toggle buttons with aria-pressed; repost and share are menu buttons; views opens a labelled dialog. Each label carries its full count ('Like, 1,204 likes'), so counts can hide visually without being lost. Every change and every rollback is announced politely with its reason. Disabled reply keeps focus and explains itself via aria-describedby. Shortcuts are scoped to the focused post.
Responsive
The bar fills its container. Below 340px, Views moves into the Share menu; below 220px, counts hide and stay in the labels. Menus open inward from the edge they sit on.
On touch
Every action is a tap target of at least 36px; the reply reason shows on focus as well as hover, and there's nothing that needs hover.

Loading components…