{
"$type": "site.standard.document",
"canonicalUrl": "https://bradestey.com/writing/stacked-cards-with-tailwind-css/",
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreiewaystzz25snaw3llos6qqctxjzht7zkokka3ok7it6nwb26dgoi"
},
"mimeType": "image/png",
"size": 61765
},
"description": "Animated stacked notification cards using only Tailwind CSS",
"path": "/writing/writing/stacked-cards-with-tailwind-css",
"publishedAt": "2025-04-19T04:00:00.000Z",
"site": "at://did:plc:gjryzxv62p6dfiy4knry3mzt/site.standard.publication/3mn73eclrks2d",
"tags": [
"design"
],
"textContent": "In the early <abbr>2010</abbr>s, I sat in a meeting with a mobile app start-up who wanted to disrupt business cards. For some context, the Apple App Store launched in July <abbr>2008</abbr> and for years after the software landscape was littered with fly-by-night “disruptors.” Never before was it easier to get software into the pockets of everyday people. It spurred a lot of useful products and it also brought out the grifters. This start-up created an app that encouraged people to create and share custom cards that featured the person’s name and photo on the front and when tapped to flip, the person’s contact info and other “stats” were shown, like a baseball card.\n\nThis was the height of skeuomorphism. The Find My Friends app still had its stitched interface. One unforseen problem with skeuomorphic design patterns was that people who had never even considered building software before confused the metaphor for innovation. This card app start-up _(I don’t even remember its name)_ failed before it even started. It wasn’t a business, it was just a user interface experiment.\n\nBut, skeuomorphism lives on! Cards are still a useful design pattern. They can be swiped away, flipped, unfolded, stacked. Notification toasts are one of the most popular examples of this pattern. Here’s an example of how to stack some notification cards using only Tailwind <abbr title=\"Cascading style sheet\">CSS</abbr>.\n\nNotifications\n\nThe first step is to build a basic notification card. Here's an example of one that looks pretty convincing.\n\nI’m using dummy json to fill in five notifications. This expanding notifications component will assume that there will never be more than five notifications. Once you try to show more than that, you’re going to need to use refs to inspect the rendered <abbr title=\"Document Object Model\">DOM</abbr> elements to get heights and use style props. More on that later. For now, here are five cards.\n\nStacking\n\nTo stack the cards, wrap the NotificationCard components in a div with a flex-col. Set overflow-y-hidden and max-h-32 to hide the extra space left from the translations.\n\nAdd the following translate and\nscale classes to the NotificationCard's\nfigure. The translate-z-px class is needed to address some z-index\nissues in Safari. Replace the bg-slate-100 with bg-slate-400.\n\nNow your notifications will stack like the example below. The first three items are visible and the last two are translated up enough to be behind the first three items.\n\nState\n\nNow for handling expanding and collapsing the notifications, add a checkbox input element. Use the peer class to share the checked state from the label with the NotificationCard components. Add has-checked:max-h-[1000px] to the containing div to make sure that the container expands enough to show all five notifications.\n\nTo the NotificationCard component, add not-first-of-type:peer-has-checked:bg-slate-100 to make all the notification backgrounds change to the default background color when the checkbox is checked. Then use not-first-of-type:peer-has-checked:translate-y-0 not-first-of-type:peer-has-checked:scale-100 to translate the notifications back to their natural location and scaled back to 100%. After that, the basic functionality is all there.\n\nAnimations\n\nAdding animations is the easiest part. To the main container div, add transition-[max-height] duration-300 ease-in-out to animate the max-height of the container. Then add transition duration-300 ease-in-out to the figure inside the NotificationCard component as well.\n\nFinal Result\n\nBelow is the completed NotificationStack component.\n\n<iframe src=\"https://codesandbox.io/embed/863ypy?view=preview&module=%2Fsrc%2FApp.tsx&hidenavigation=1\"\n style=\"width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;\"\n title=\"stacked-cards-with-tailwind-css\"\n allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n ></iframe>\n\nThe final result can be forked on CodeSandbox. It has a little extra work done to style the expand/collapse buttons and adds focus and hover states.\n\nHere’s all the code for reference:\n\nIf you want to expand more cards, an unknown number of cards or variable height cards, then you’re going to need to add a ref to detect each card’s height and relative distance from the top of its container and then use style props like style={{ 'transform': 'translateY(-120px)' }}. Usually, like in the case of notification toasts, you want to keep these cards at a consistent height to keep to a manageable predictable amount of screen real-estate. But, other implementations may have different requirements.",
"title": "Stacked Cards with Tailwind CSS"
}