Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
55
Whether you've barely scratched the surface of keyframe animations in CSS or fancy yourself as a seasoned pro, I suggest reading An Interactive Guide to Keyframe Animations. Josh (as always) does an impeccable deep dive that includes interactive demos for multi-step animations, loops, setting dynamic values, and more. This is a quick post pointing out some other minor particulars: Duplicate keyframe properties The order of keyframe rules Custom timing function (easing) values at specific keyframes Duplicate keyframe properties Imagine an "appearance" animation where an element slides down, scales up, and changes color. The starting 0% keyframe sets the element's y-axis position and scales down the size. The element glides down to its initial position for the full duration of the animation. About halfway through, the element's size is scaled back up and the background color changes. At first, we might be tempted to duplicate the background-color and scale properties in both 0% and 50%...
a month ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Ryan Mulligan

Scrolling Rails and Button Controls

Once again, here I am, hackin' away on horizontal scroll ideas. This iteration starts with a custom HTML tag. All the necessities for scroll overflow, scroll snapping, and row layout are handled with CSS. Then, as a little progressive enhancement treat, button elements are connected that scroll the previous or next set of items into view when clicked. Behold! The holy grail of scrolling rails... the scrolly-rail! CodePen demo GitHub repo Open CodePen demo I'm being quite facetious about the "holy grail" part, if that's not clear. 😅 This is an initial try on an idea I'll likely experiment more with. I've shared some thoughts on potential future improvements at the end of the post. With that out of the way, let's explore! The HTML Wrap any collection of items with the custom tag: <scrolly-rail> <ul> <li>1</li> <li>2</li> <li>3</li> <!-- and so on--> </ul> </scrolly-rail> The custom element script checks if the direct child within scrolly-rail is a wrapper element, which is true for the above HTML. While it is possible to have items without a wrapper element, if the custom element script runs and button controls are connected, sentinel elements are inserted at the start and end bounds of the scroll container. Wrapping the items makes controlling spacing between them much easier, avoiding any undesired gaps appearing due to these sentinels. We'll discover what the sentinels are for later in the post. The CSS Here are the main styles for the component: scrolly-rail { display: flex; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; @media (prefers-reduced-motion: no-preference) { scroll-behavior: smooth; } } When JavaScript is enabled, sentinel elements are inserted before and after the unordered list (<ul>) element in the HTML example above. Flexbox ensures that the sentinels are positioned on either side of the element. We'll find out why later in this post. Containing the overscroll behavior will prevent us accidentally triggering browser navigation when scrolling beyond either edge of the scrolly-rail container. scroll-snap-type enforces mandatory scroll snapping. Smooth scrolling behavior applies when items scroll into view on button click, or if interactive elements (links, buttons, etc.) inside items overflowing the visible scroll area are focused. Finally, scroll-snap-align: start should be set on the elements that will snap into place. This snap position aligns an item to the beginning of the scroll snap container. In the above HTML, this would apply to the <li> elements. scrolly-rail li { scroll-snap-align: start; } As mentioned earlier, this is everything our component needs for layout, inline scrolling, and scroll snapping. Note that the CodePen demo takes it a step further with some additional padding and margin styles (check out the demo CSS panel). However, if we'd like to wire up controls, we'll need to include the custom element script in our HTML. The custom element script Include the script file on the page. <script type="module" src="scrolly-rail.js"></script> To connect the previous/next button elements, give each an id value and add these values to the data-control-* attributes on the custom tag. <scrolly-rail data-control-previous="btn-previous" data-control-next="btn-next" > <!-- ... --> </scrolly-rail> <button id="btn-previous" class="btn-scrolly-rail">Previous</button> <button id="btn-next" class="btn-scrolly-rail">Next</button> Now clicking these buttons will pull the previous or next set of items into view. The amount of items to scroll by is based on how many are fully visible in the scroll container. For example, if we see three visible items, clicking the "next" button will scroll the subsequent three items into view. Observing inline scroll bounds Notice that the "previous" button element in the demo's top component. As we begin to scroll to the right, the button appears. Scrolling to the end causes the "next" button to disappear. Similarly, for the bottom component we can see either button fade when their respective scroll bound is reached. Recall the sentinels discussed earlier in this post? With a little help from the Intersection Observer API, the component watches for either sentinel intersecting the visible scroll area, indicating that we've reached a boundary. When this happens, a data-bound attribute is toggled on the respective button. This presents the opportunity to alter styles and provide additional visual feedback. .btn-scrolly-rail { /** default styles */ } .btn-scrolly-rail[data-bound] { /* styles to apply to button at boundary */ } Future improvements I'd love to hear from the community most specifically on improving the accessibility story here. Here are some general notes: I debated if button clicks should pass feedback to screen readers such as "Scrolled next three items into view" or "Reached scroll boundary" but felt unsure if that created unforeseen confusion. For items that contain interactive elements: If a new set of items scroll into view and a user tabs into the item list, should the initial focusable element start at the snap target? This could pair well with navigating the list using keyboard arrow keys. Is it worth authoring intersecting sentinel "enter/leave" events that we can listen for? Something like: Scroll bound reached? Do a thing. Leaving scroll bound? Revert the thing we just did or do another thing. Side note: prevent these events from firing when the component script initializes. How might this code get refactored once scroll snap events are widely available? I imagine we could check for when the first or last element becomes the snap target to handle toggling data-bound attributes. Then we can remove Intersection Observer functionality. And if any folks have other scroll component solutions to share, please reach out or open an issue on the repo.

a month ago 59 votes
The Shape of Runs to Come

Over the last few months or so, I have been fairly consistent with getting outside for Sunday morning runs. A series of lower body issues had prevented me from doing so for many years, but it was an exercise I had enjoyed back then. It took time to rebuild that habit and muscle but I finally bested the behavior of doing so begrudgingly. Back in the day (what a weird phrase to say, how old am I?) I would purchase digital copies of full albums. I'd use my run time to digest the songs in the order the artist intended. Admittedly, I've become a lazy listener now, relying on streaming services to surface playlists that I mindlessly select to get going. I want to be better than that, but that's a story for another time. These days, my mood for music on runs can vary: Some sessions I'll pop in headphones and throw on some tunes, other times I head out free of devices (besides a watch to track all those sweet, sweaty workout stats) and simply take in the city noise. Before I headed out for my journey this morning, a friend shared a track from an album of song covers in tribute to The Refused's The Shape Of Punk To Come. The original is a treasured classic, a staple LP from my younger years, and I can still remember the feeling of the first time it struck my ears. Its magic is reconjured every time I hear it. When that reverb-soaked feedback starts on Worms of the Senses / Faculties of the Skull, my heart rate begins to ascend. The anticipation builds, my entire body well aware of the explosion of sound imminent. As my run began, I wasn't sure if I had goosebumps from the morning chill or the wall of noise about to ensue. My legs were already pumping. I was fully present, listening intently, ready for the blast. The sound abruptly detonated sending me rocketing down the street towards the rising sun. My current running goal is 4-in-40, traversing four miles under forty minutes. I'm certainly no Prefontaine, but it's a fair enough objective for my age and ability. I'll typically finish my journey in that duration or slightly spill over the forty-minute mark. Today was different. Listening to The Shape Of Punk To Come sent me cruising an extra quarter mile beyond the four before my workout ended. The unstoppable energy from that album is truly pure runner's fuel. There's certainly some layer of nostalgia, my younger spirit awakened and reignited by thrashing guitars and frantic rhythms, but many elements and themes on this record were so innovative at the time it was released. New Noise is a prime example that executes the following feeling flawlessly: Build anticipation, increase the energy level, and then right as the song seems prepped to blast off, switch to something unexpected. In this case, the guitars drop out to make way for some syncopated celestial synths layered over a soft drum rhythm. The energy sits in a holding pattern, unsure whether it should burst or cool down, when suddenly— Can I scream?! Oh my goodness, yes. Yes you can. I quickly morphed into a runner decades younger. I had erupted, my entire being barreling full speed ahead. The midpoint of this track pulls out the same sequence of build up, drop off, and teasing just long enough before unleashing another loud burst of noise, driving to its explosive outro. As the song wraps up, "The New Beat!" is howled repeatedly to a cheering crowd that, I would imagine, had not been standing still. I definitely needed a long stretch after this run.

2 months ago 36 votes
The Pixel Canvas Shimmer Effect

I recently stumbled on a super cool, well-executed hover effect from the clerk.com website where a bloom of tiny pixels light up, their glow staggering from the center to the edges of its container. With some available free time over this Thanksgiving break, I hacked together my own version of a pixel canvas background shimmer. It quickly evolved into a pixel-canvas Web Component that can be enjoyed in the demo below. The component script and demo code have also been pushed up to a GitHub repo. Open CodePen demo Usage Include the component script and then insert a pixel-canvas custom element inside the container it should fill. <script type="module" src="pixel-canvas.js"></script> <div class="container"> <pixel-canvas></pixel-canvas> <!-- other elements --> </div> The pixel-canvas stretches to the edges of the parent container. When the parent is hovered, glimmering pixel fun ensues. Options The custom element has a few optional attributes available to customize the effect. Check out the CodePen demo's html panel to see how each variation is made. data-colors takes a comma separated list of color values. data-gap sets the amount of space between each pixel. data-speed controls the general duration of the shimmer. This value is slightly randomized on each pixel that, in my opinion, adds a little more character. data-no-focus is a boolean attribute that tells the Web Component to not run its animation whenever sibling elements are focused. The animation runs on sibling focus by default. There's likely more testing and tweaking necessary before I'd consider using this anywhere, but my goal was to run with this inspiration simply for the joy of coding. What a mesmerizing concept. I tip my hat to the creative engineers over at Clerk.

2 months ago 36 votes
Web Components for Password Input Enhancements

So there I was, experimenting with HTML password inputs and Web Components. I'm not sure why the idea even came up but it quickly snowballed into a curious expedition. The result from the journey was a set of custom elements that provide extra functionality and information about the text being typed into a password input field. I shared my CodePen demo in a Mastodon post and soon after decided to push these scripts up to a GitHub repo. Open CodePen demo Get started The repo includes two Web Component scripts. They operate independent of one another. I recommend reading through the repo documentation but here's a rundown of what's included. <password-rules> adds an input event listener to capture when a list of rules (password length, includes an uppercase letter, etc.) are matched as the user is typing in their new password. <password-toggle> shows and hides the password input value on click. To get started, add the scripts to a project and include them on the page. <script type="module" src="path/to/password-rules.js"></script> <script type="module" src="path/to/password-toggle.js"></script> Below is an example of using both custom elements with a password input. <label for="new-password">Password</label> <input type="password" id="new-password" /> <div id="status" aria-live="polite"></div> <password-toggle data-input-id="new-password" data-status-id="status"> <button type="button">Toggle password visibility</button> </password-toggle> <password-rules data-input-id="new-password" data-rules=".{9}, [A-Z], .*\d"> <ul> <li data-rule-index="0">Longer than 8 characters</li> <li data-rule-index="1">Includes an uppercase letter</li> <li data-rule-index="2">Includes a number</li> </ul> </password-rules> Password toggle password-toggle expects a button element to be inside it. This button will be augmented with the ability to toggle the visibility of the input field's value. When the toggle button is clicked, the "status" element containing the aria-live attribute will send a notification to screen readers that the password value is currently visible or hidden. For instance, when clicking for the first time, the string "Password is visible" is inserted into the container and announced by a screen reader. We can also style the toggle button when it enters its pressed or "visible password" state. In the CodePen demo, this is how the eye icon (aye aye!) is being swapped. button svg:last-of-type { display: none; } button[aria-pressed="true"] { svg:first-of-type { display: none; } svg:last-of-type { display: block; } } Targeting the [aria-pressed] attribute selector ensures that our styles stay in sync with their accessibility counterpart. It also means that we don't need to manage a semantic attribute value as well as some generic class selector like "is-active". Ben Myers shares great knowledge on this subject in Style with stateful, semantic selectors. A must-have in the bookmarks 🏆 Password rules The password-rules element is passed a comma-separated list of regular expression strings, each related to a specific rule. We also have the option to connect any child element to a rule by passing the index of that string to a data-rule-index attribute. The placement or type of element doesn't matter as long as it's contained within the password-rules. Here's an alternate version to drive that point home: <password-rules data-input-id="new-password" data-rules=".{8}, [A-Z], .*\d"> <div class="one-column" data-rule-index="0"> Longer than 8 characters </div> <div class="two-columns"> <span data-rule-index="2">Includes a number</span> <span data-rule-index="1">Includes an uppercase letter</span> </div> </password-rules> Check it off When a rule is met that matches the data-rule-index value on an element, an is-match class gets added to the element. The demo styles use this selector to add a checkmark emoji when present. .password-rules__checklist .is-match::before { content: "✅"; } score/total The current password "score" and rules "total" are passed to the custom element as data attributes and CSS variables. The score value updates as rules are met. This allows us to do some fancy things like change the colors in a score meter and present the current tally. All of it done with CSS. /** Incrementally adjust background colors */ password-rules[data-score="1"] .password-rules__meter :first-child, password-rules[data-score="2"] .password-rules__meter :nth-child(-n + 2), password-rules[data-score="3"] .password-rules__meter :nth-child(-n + 3), password-rules[data-score="4"] .password-rules__meter :nth-child(-n + 4) { background-color: dodgerblue; } /** When all rules are met, swap to a new color for each meter element */ password-rules[data-score="5"] .password-rules__meter :nth-child(-n + 5) { background-color: mediumseagreen; } CSS variables are passed into a CSS counter() to render the current score and total. .password-rules__score::before { counter-reset: score var(--score, 0) total var(--total, 5); content: counter(score) "/" counter(total); } I added fallback values to the CSS variables when I realized that the --total value, specifically, renders as 0 on page load and doesn't update until we begin typing in the input field. I did discover that we could skip the fallback by registering the custom property. This ensures the total is correctly reflected when the component initializes. But, to be honest, this feels unnecessary when the fallback here will suffice. @property --total { syntax: "<number>"; initial-value: 0; inherits: true; } If this @property stuff is unfamiliar, Stephanie Eckles has got you covered in Providing Type Definitions for CSS with @property. Another one to bookmark! I've also recently spent time with this newly supported at-rule in CSS @property and the New Style. Progressively enhanced for the win I believe this tells a fairly nice progressive enhancement story. Without JavaScript, the password input still works as expected. But when these scripts run, users get additional feedback and interactivity. Developers get access to extra selectors that can be useful for styling state changes. And listen, I get it–there are better ways to handle client-side form validation, but this was a fun exploration nonetheless.

4 months ago 23 votes

More in design

Software Pliability

Quoting myself from former days on Twitter: Businesses have a mental model of what they do. Businesses build software to help them do it—a concrete manifestation of their mental model. A gap always exists between these two. What makes a great software business is their ability to keep that gap very small. I think this holds up. And I still think about this idea (hence this post). Software is an implementation of human understanding — people need X, so we made Y. But people change. Businesses change. So software must also change. One of your greatest strengths will be your ability to adapt and evolve your understanding of people’s needs and implement it in your software. In a sense, technical debt is the other side of this coin of change: an inability to keep up with your own metamorphosis and understanding. In a way, you could analogize this to the conundrum of rocket science: you need fuel to get to space, but the more fuel you add, the more weight you add, and the more weight you add, the more fuel you need. Ad nauseam. It’s akin to making software. You want to make great software for people’s needs today. It takes people, processes, and tools to make software, but the more people, processes, and tools you add to the machine of making software, the less agile you become. So to gain velocity you add more people, processes, and tools, which…you get the idea. Being able to build and maintain pliable software that can change and evolve at the same speed as your mental model is a superpower. Quality in code means the flexibility to change. Email :: Mastodon :: Bluesky

yesterday 3 votes
Mother’s Magic by Mihir Sheth

Mother’s Magic is a premium spice brand offering a range of carefully crafted spice blends. Each blend is made from...

yesterday 2 votes
The Internet Can't Discover: A Case for New Technologies

The internet reflects us. But new technologies must explore the world beyond. We’ve spent more than two generations and trillions of dollars building the internet. It is, arguably, humanity’s most ambitious technological project. And yet, for all its power to process and reflect information, the internet cannot tell us a single new thing about the physical world around us. The internet cannot detect an approaching asteroid, discover a new species in the deep ocean, or detect changes in the Earth’s magnetic field. It cannot discover a new material or invent something new from it. The internet is, fundamentally, an introspective technology; it is a mirror, showing us only what we’ve already put into it. What we desperately need are more extrospective technologies — windows into the unknown. Consider what the internet actually does: it processes and transmits information that humans have already discovered, documented, and digitized. Even the most sophisticated applications it enables — like artificial intelligence — can only recombine and reinterpret existing information about ourselves and our own creations. They cannot generate genuinely new knowledge about the physical world. (Let’s debate whether pattern recognition truly fits the bill in another pieces.) When we marvel at AI’s capabilities, we’re really just admiring an increasingly sophisticated form of introspection. So what is an example of an extrospective technology? The James Webb Space Telescope is a good one. It offers a striking contrast to the internet’s introspective limitations. While we spend billions optimizing ways to look at ourselves, Webb actually shows us something new about our universe — it shows us more of it that we’ve ever seen before. Every image it sends back is a discovery — whether it’s the atmospheric composition of distant planets, the formation of early galaxies, or phenomena we hadn’t even thought to look for. Unlike the internet, which can only reflect what we already know about ourselves, Webb is quite literally a window for looking outward. It extends our vision not just beyond our natural capabilities, but beyond what any human has ever seen before. The imbalance between introspective and extrospective technologies in our society is striking. The entire James Webb Space Telescope project cost about $10 billion — roughly what Meta spends on its metaverse project in a single year. We have more engineers working on optimizing our digital reflections than on all of our outward-looking scientific instruments combined. The most talented minds of our generation are focused on perfecting the mirror rather than opening new windows to the universe. Of course, this isn’t just about how we spend our money — it’s about our relationship with discovery itself. The internet has made us incredibly efficient at examining and reprocessing our own knowledge, but it may have dulled our appetite for looking outward. We’ve become so accustomed to the immediate gratification of accessing existing information that we’ve forgotten the importance of the long, patient work of discovery. The technologies that can actually tell us something new about our world often operate on different timescales than introspective ones. A deep ocean sensor might need years to reveal meaningful patterns. A space telescope might require decades of operation before making a groundbreaking discovery. These extrospective technologies demand patience and sustained investment — qualities that our internet-shaped attention spans increasingly struggle with. Yet these are exactly the technologies we need most urgently. As we face unprecedented environmental challenges and explore new frontiers in space, we need more windows into the physical world, not better mirrors of our digital one. We need technologies that can warn us of environmental changes, reveal new resources, and help us understand our place in the universe. As enthusiastic as I am about things like the James Webb Telescope, I’m also fascinated by how much we still don’t know about this planet. Only 26% of the ocean floor has been mapped using high resolution sonar technology. Only 5% of the ocean has actually been physically explored. It has been estimated that the ocean’s ecosystem is home to somewhere between 700,000 and 1 million unique species. The vast majority of them — greater than 60% — remain undiscovered. Isn’t it astonishing how little we know about this thing that covers 70% of our planet’s surface? What could we create to learn more about the ocean? It has to be something truly novel — something other than a better submarine. The ocean is just one place to point an extrospective technology. Think of the greatest unknowns. How many of them will be illuminated by the internet? Will the internet expand our understanding of physics, or will it document what we find by some other means? Will the internet ever be more than a catalog of discoveries? Will the internet explain the nature of time, or will it just continue to draw from our finite supply of it? The internet will remain vital infrastructure, but it’s time to rebalance our technological investment. We need to direct more resources and talent toward extrospective technologies — tools that can tell us something new about the world beyond our screens. Only then can we move beyond endless introspection and toward genuine discovery of the universe that exists whether we look at it or not.

3 days ago 4 votes
Blown Away By the Unexpected

A friend gave me a copy of the book “Perfect Wave” by Dave Hickey. I’ve been slowly reading through each essay and highlighting parts with my red pencil. When I got to the chapter “Cool on Cool”, this passage stood out. I want to write it down and share it: there was this perfect, luminous pop single by the Carpenters that just blew me away. And, believe me, the Carpenters were the farthest thing from my kind of thing. But when something that is not your thing blows you away, that’s one of the best things that can happen. It means you are something more and something other than you thought you were. I find this beautiful. I should take more time to wonder at moments of surprise I did not expect. What a beautiful thing that I can be plowing through my existence and suddenly be surprised by something outside my taste, my beliefs, even my identity, that reaches in past all those things and rearranges me. Perhaps my boundaries are more porous than I assume. In an instant, I can become something different, something more that I ever believed was possible. Just think, that ability is lying there inside all of us. I don’t have to think of myself as a walled garden but an open field. Who knows where my boundaries will expand to next. All it takes is someone walking by and tossing out a seed I would’ve never chosen to plant myself. (Tangential: I love this interaction between Jerry Seinfeld and Brian Regan talking about being “blown away”.) Email :: Mastodon :: Bluesky

4 days ago 5 votes
A Workplace in Perpetual Beta: A Design that Adapts, Inspires, and Evolves

In the heart of Bengaluru, a global asset management leader envisioned more than just a workspace—it imagined a sanctuary of...

4 days ago 4 votes