One draft, every channel.
Drivable beats watchable
Most product homepages show you a feature playing on a loop. A cursor slides across the screen on an invisible rail, something updates, and you sit and watch a recording of someone else using the thing. It's fine. It also tells you almost nothing about how the thing feels, because you never touched it.
I kept coming back to that while looking at how a tool like Buffer previews a post across channels. The whole job is reassurance — show me what my words turn into on each platform before I commit them. That's a promise you can't keep with a video. So I built the smallest honest version I could: one text box, a few platform previews, and the rule that everything updates the instant you type. Nothing here is on rails. You drive it.
Three platforms, three honest rules
The temptation with something like this is to chase pixels, to rebuild each app's exact card down to the icon set. That's the wrong kind of effort. What matters is behaviour, and each platform has one rule worth being faithful to.
- X counts. A hard limit of 280, a count that ticks down as you type, and the moment you cross zero it goes negative and red and the overflow greys out — the words are still there, they just won't post. The count uses tabular figures so it doesn't jitter as the digits change underneath it.
- Instagram hides. The caption keeps going, but the preview folds it at around 125 characters and leaves a muted "… more" where it stopped.
- LinkedIn does the same a little later, near 140, with its own "…see more".
All three pick out #hashtags and @mentions and tint them in the platform's accent, and all three keep your line breaks rather than flattening them into one grey paragraph. I made a small call on the trimming: instead of slicing dead on the character count and cutting a word in half, it steps back to the last space when one is close by. It reads like a sentence someone stopped writing, not a string that ran out of room.
The reflow is the feeling
The part I spent the most time on is the bit you would never name out loud. When a card grows as you type, or leaves because you toggled it off, the others don't jump to their new places — they glide. That movement is most of the sense of the thing being alive, and it's one Motion layout prop doing the work, backed by a single spring.
I tuned that spring by feel. I started at a stiffness of 300, which settled with the faintest tick of overshoot on a big paste. Dropping it to 260 turned the settle into a glide that arrives once and stops. Every motion value in the piece — that spring, the 0.22-second card fade, the 0.15-second colour shift on the counter — lives in one motion.ts file. Motion deserves a scale the same way colour and spacing do, and keeping the numbers in one place is how you stop a dozen components quietly disagreeing about what "quick" means.
When someone can't see motion
A preview that lurches is worse than one that holds still, so the first thing the piece checks is whether the person has asked their system to calm motion down. If they have, every transform and spring switches off. The cards appear and reflow instantly, the counter still recolours, and not a single thing slides. The interaction is identical. It just stops moving.
The character count is read to screen readers too, but politely and rarely. Announcing the remaining number on every keystroke would be its own small torture, so the announcement only fires when you cross the limit — over, and then back under. That is the moment that actually carries information.
What I left out
Restraint was the brief, so the list of things this deliberately doesn't do is longer than the list of things it does. No scheduling, no publish button, no accounts. The "… more" affordances don't expand, because the point is to show you where each platform folds your caption, not to rebuild each platform's reader. Three channels, and a full stop after the third.
I staged it inside a small product window, too. A composer reads better as one screen of a tool than as a control floating in space, and the sidebar earns its place by being honest about scope: one route is real, the one you're on, and the rest are skeleton rows that never resolve. That's the most direct way I know to say the other screens are out of scope rather than unfinished. The job was to draw the one screen that had to feel right, not the whole product.
The one piece of scaffolding I did build is the frame around the demo. Every craft piece on this site can now open full screen through a single shared DemoFrame, a real dialog with proper focus handling rather than a div doing an impression of one, because interaction work is genuinely hard to judge in a column seven hundred pixels wide. It felt worth doing once, properly, for all of them.
A preview only works if you trust it, and the way you come to trust it is by driving it yourself — typing your own clumsy first draft and watching it land the same way on every channel at once. A recording on a loop was never going to do that.