{"id":10561,"date":"2026-07-29T17:08:55","date_gmt":"2026-07-29T22:08:55","guid":{"rendered":"https:\/\/master.dev\/blog\/?p=10561"},"modified":"2026-07-29T17:08:56","modified_gmt":"2026-07-29T22:08:56","slug":"a-button-that-links-to-a-random-post-in-html-css-only","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/a-button-that-links-to-a-random-post-in-html-css-only\/","title":{"rendered":"A Button That Links to a Random Post in HTML &amp; CSS Only"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I was reading a post by Jim Nielsen where he was <a href=\"https:\/\/blog.jim-nielsen.com\/2026\/notes-shuffle\/\">Making a Shuffle Button<\/a>. It was for a static site, so there was no server-side code to hit to redirect to a random post. PHP, for instance, could query for all posts and select one pseudo-randomly, and redirect there. But no such language on a static site.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Take One<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instead, Jim did this (at first):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- In the site navigation --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"js-shuffle\"<\/span>&gt;<\/span>Shuffle<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- Way down at the end of the HTML --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n  \/\/ All 974 note IDs injected by my SSG\n  const noteIds = &#91;'id-1', 'id-2', id-3', 'id-4', '...'];\n  document.querySelector(\"#js-shuffle\")\n    .addEventListener('click', () =&gt; {\n      \/\/ randomly grab an item in `noteIds`\n      const randomId = '...';\n      window.location.href = `\/n\/${randomId}\/`\n    })\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The issue with this approach is that Jim inlined it on every single HTML page and thus every page had to be re-built during deployment if a new post was published. I do feel like saying JIM THIS COULD HAVE BEEN A WEB COMPONENT and built as a single file and imported, but I&#8217;ll refrain. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then Jim went through 3-4 other techniques that also worked, each with its ups and downs. I like the technique Jim landed on, which was making a single page that handled the job and redirected away. Basically like the solution above, just at a <code>shuffle.html<\/code> page and ran automatically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;m afraid I don&#8217;t have an\u00a0<em>improvement<\/em>\u00a0for Jim, per se, but it made me think of another potential solution. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What if it depended on <em>where<\/em> you clicked the random button?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I mean, we&#8217;re talking about a physical button here, right? So I&#8217;m talking literally what pixel you clicked on on the button would determine which random post you go to.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"658\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-29-at-2.26.22-PM.png?resize=1024%2C658&#038;ssl=1\" alt=\"\" class=\"wp-image-10567\" style=\"width:591px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-29-at-2.26.22-PM.png?resize=1024%2C658&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-29-at-2.26.22-PM.png?resize=300%2C193&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-29-at-2.26.22-PM.png?resize=768%2C493&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/Screenshot-2026-07-29-at-2.26.22-PM.png?w=1264&amp;ssl=1 1264w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">We could get these coordinates in JavaScript, but I don&#8217;t think we need to. We can just fill the area with <code>&lt;a><\/code> links that go to the posts. So it&#8217;s not a single button, it&#8217;s just a button-like shape full of tiny buttons (links, actually). <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To this, we could do like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"random-button\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/post\/1\"<\/span>&gt;<\/span>Post 1<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/post\/2\"<\/span>&gt;<\/span>Post 2<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/post\/3\"<\/span>&gt;<\/span>Post 3<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n  ...\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>Random Post<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Then we just let the links lay out naturally within the button space:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.random-button<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">flex-wrap<\/span>: wrap;\n  <span class=\"hljs-attribute\">align-content<\/span>: stretch;\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n  <span class=\"hljs-attribute\">overflow<\/span>: hidden;\n\n  <span class=\"hljs-comment\">\/* make it look like a button *\/<\/span>\n  <span class=\"hljs-attribute\">inline-size<\/span>: <span class=\"hljs-number\">300px<\/span>;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">14px<\/span>;\n\n  &gt; a {\n    <span class=\"hljs-comment\">\/* whatever sizing works *\/<\/span>\n    <span class=\"hljs-attribute\">flex<\/span>: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-number\">1<\/span> <span class=\"hljs-number\">30px<\/span>;\n  }\n\n  &gt; <span class=\"hljs-selector-tag\">span<\/span> {\n    <span class=\"hljs-attribute\">pointer-events<\/span>: none;\n    <span class=\"hljs-attribute\">position<\/span>: absolute;\n    <span class=\"hljs-attribute\">inset<\/span>: <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-attribute\">display<\/span>: grid;\n    <span class=\"hljs-attribute\">place-items<\/span>: center;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This flexbox setup, with the stretchy alignment and flex-grow allowance, should fill the space nicely.<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe loading=\"lazy\" title=\"VideoPress Video Player\" aria-label=\"VideoPress Video Player\" width=\"500\" height=\"213\" src=\"https:\/\/videopress.com\/embed\/cafDxUQS?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0\" frameborder=\"0\" allowfullscreen data-resize-to-parent=\"true\" allow=\"clipboard-write; presentation\"><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1770107250'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p class=\"wp-block-paragraph\">See how the last row always fills the space there no matter how many links there are? A &#8220;button&#8221; should probably respond no matter where it is clicked, and now we can do that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want this implementation to be a secret, you could set <code>opacity: 0;<\/code> on the links, and the behavior will be the same and the whole thing will just look like a single button.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Gettin&#8217; More Random<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With one line, we can make this all a bit more random. I&#8217;d say it&#8217;s still <em>pretty<\/em> random without this, as where you click exactly will essentially be random. But this is better.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And check it out, it&#8217;s a one liner&#8230;<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-code-table\"><span class='shcb-loc'><span>.random-button {\n<\/span><\/span><span class='shcb-loc'><span>  ...\n<\/span><\/span><span class='shcb-loc'><span>  reading-flow: flex-visual;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  &gt; a {\n<\/span><\/span><mark class='shcb-loc'><span>    order: random(1, 9999);\n<\/span><\/mark><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Flexbox supports the <code>order<\/code> property. Sometimes it&#8217;s a finger-waggle thing because you&#8217;re messing with the visual tabbing order when you use it, which can be an accessibility issue. But&#8230;<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>These are kinda hidden secret links so maybe it doesn&#8217;t matter?<\/li>\n\n\n\n<li>We can fix it with <code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Reference\/Properties\/reading-flow\">reading-flow<\/a>: flex-visual;<\/code> on the button anyway.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Now, with CSS alone, the link placement is random. <a href=\"https:\/\/master.dev\/blog\/more-css-random-learning-through-experiments\/\">That <code>random()<\/code> function<\/a> works in Safari and Chrome-with-flag, so I did up some colors with it too to make it clearer. <\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player\" style=\"\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe loading=\"lazy\" title=\"VideoPress Video Player\" aria-label=\"VideoPress Video Player\" width=\"500\" height=\"313\" src=\"https:\/\/videopress.com\/embed\/sQHSJCMl?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=1&amp;persistVolume=0&amp;playsinline=1&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0\" frameborder=\"0\" allowfullscreen data-resize-to-parent=\"true\" allow=\"clipboard-write; presentation\"><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1770107250'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s client-side randomization. Every page load will be different.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Demo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The demo has some bonus stuff like setting the <code>font-size<\/code> with container units so things kinda fit nicely in the space. Maybe you&#8217;d do some fancy build-time math to size things based on how many links there are. I&#8217;d think this would scale up into the thousands if you&#8217;re down to like 1px \u00d7 1px links.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_019f9a6f-854e-7424-a068-d9a4eb202fc2\" src=\"\/\/codepen.io\/editor\/anon\/embed\/019f9a6f-854e-7424-a068-d9a4eb202fc2?height=450&amp;theme-id=1&amp;slug-hash=019f9a6f-854e-7424-a068-d9a4eb202fc2&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed 019f9a6f-854e-7424-a068-d9a4eb202fc2\" title=\"CodePen Embed 019f9a6f-854e-7424-a068-d9a4eb202fc2\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;d tell Jim to give it a shot, but I think the route he landed on, where a single link does the randomization, is probably a little more useful in his case.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Perhaps this theoretical button wasn&#8217;t a button at all, but 50 links in a trenchcoat.<\/p>\n","protected":false},"author":1,"featured_media":10575,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[208,7,344,388],"class_list":["post-10561","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-blogging","tag-css","tag-flexbox","tag-random"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/random-post-1.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10561","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/comments?post=10561"}],"version-history":[{"count":3,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10561\/revisions"}],"predecessor-version":[{"id":10576,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10561\/revisions\/10576"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10575"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=10561"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=10561"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=10561"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}