Defensive CSS

フィード

記事のアイキャッチ画像
Button minimum width
Defensive CSS
A common mistake is to assume that a button width should be as equal to its content plus the horizontal padding. This might work as expected for a one-language website (e.g: English) but it can easily fail with multilingual websites.Consider the following example.On the left, the button width works well since the word "Done" is long enough. However, in Arabic, it's translated to "تم" and thus the button width became too small. From a UX perspective, this isn't good as a call to action button must be large enough, specially for touch.To avoid that, we can set a minimum width for the button in advance..button { min-width: 90px;}That way, the button will have a fixed minimum width that can grow if the button's label is longer.Play with the demo below to see the issue and the fix in action.Done تم Toggle Defensive To learn more about writing CSS for RTL (Right-to-left), I wrote a complete guide on that.
2年前
記事のアイキャッチ画像
Input zoom on iOS Safari
Defensive CSS
When focusing an input in iOS Safari, the whole web page will zoom by default. This is a common behavior in Safari. It's annoying, since when zooming the page, it won't zoom out as the input is no longer focused.The solution is fairly simple, which is to add font-size: 16px to the input.input { font-size: 16px;}By the way, zooming in is a good thing. Some users might prefer to see what they're typing in a large text, but what annoys me personally is that Safari only zoom-in, and left the user figure out themselves to zoom-out. That's why I would prefer to reset that early on.
2年前
記事のアイキャッチ画像
Default flexbox stretching
Defensive CSS
In flexbox, the default behaviour for flex items is to stretch. If a child item has content longer than its sibling, this will cause the other item to stretch.This can't be spotted easily unless we add longer content than expected to a flex item.Consider the following HTML and figure. We have a component that contains an avatar, name, and a biography.<div class="person"> <img class="person__avatar" src="img/avatar.jpg" alt=""> <div class="person__content"> <h3>Ahmad Shadeed</h3> <p><!-- Description goes here.. --></p> </div></div>.person { display: flex;}When the bio content (the .person__content element) exceeds the height of the avatar, this will result in the stretching of the avatar.To fix that, we need to override the default stretching behavior and keep the avatar aligned to the start (or the center) of its parent..person__avatar { /* other styles */ align-self: start;}Or you can use align-items: center on the parent element. Whatever works best for your case!.person { display: f
2年前
記事のアイキャッチ画像
Image inner border
Defensive CSS
When dealing with user avatars, it can be challenging to display them all in a clear way. That's because some images are too light, thus it might blend with the background underneath it, specially if it's a white one.Consider the following figure.Notice how in the first row, the second avatar is blended with the white background. However, when we have a border, it's much better.We can prevent that in advance and add an inner border to the image. Not only that, but we cal also think about dark mode too!In CSS, we can mimic the inner border by using an inset box-shadow. The challenge though is that it's not possible to add an inset shadow to an <img> element. As a result, we need to wrap the image in another element and apply the inner shadow to it.<div class="card__avatar"> <img src="assets/shadeed.jpg" alt="" /> <div class="border"></div></div>.card__avatar { position: relative;}.card__avatar img { width: 56px; height: 56px; border-radius: 50%;}.border { position: absolute; width: 56px
2年前
記事のアイキャッチ画像
Accidental hover on mobile
Defensive CSS
We use hover effects to provide an indication to the user that an element is clickable or active. That is fine for devices that have a mouse or a trackpad. However, for mobile browsing hover effects can get confusing.While scrolling in a page, your finger can accidentally do a half-tap which will trigger the hover state of a specific element.Consider the following figure.While the user is scrolling, a hover was triggered. At this point, the user didn't mean to do that at all since there is no hover on mobile.The solution for this is to use the hover media query. It's possible to detect if the user's current input tool (trackpad vs mouse) can hover over elements.@media (hover: hover) { .card:hover { /* Add hover styles.. */ }}That way, the hover style will only work when the user is using a tool device like a mouse or a trackpad.
2年前
記事のアイキャッチ画像
Vertical media queries
Defensive CSS
Sometimes, it’s so tempting to build a component and only test by resizing the browser’s width. Testing against the browser’s height can reveal some interesting problems.Here is one that I’ve seen multiple times. We have an aside component with main and secondary links. The secondary links should be positioned at the very bottom of the aside section.Consider the following example. The main and secondary navigation looks okay. In the example that I saw, the developer added position: sticky to the secondary navigation so that it can stick to the bottom.However, if the browser height is smaller, things will break. Notice how the two navigations are overlapped.By using CSS vertical media queries, we can avoid that issue.That way, the secondary navigation will only be sticked to the bottom if the viewport height is larger than or equals 600px. Much better, right?There are probably better ways to implement that behavior (like using margin-auto) but I’m focusing on the vertical query for this
2年前
記事のアイキャッチ画像
Text over image
Defensive CSS
When using the text over an imaging approach, it’s important to account for the case where the image fails to load. How the text will look like?Here is an example.The text looks readable, but when the image fails to load, it won’t.We fix that easily by adding a background color to the <img> element. This background will only be visible if the image fails to load. Isn’t that cool?.card__img { background-color: grey;}
2年前
記事のアイキャッチ画像
Using space-between
Defensive CSS
In a flex container, you might use justify-content to space the child items from each other. With a certain number of child items, the layout will look okay. However, when they increase or decrease, the layout will look odd.Consider the following example.We have a flex container with four items. The spacing between each item isn’t a gap or margin, it’s there because the container has justify-content: space-between..wrapper { display: flex; flex-wrap: wrap; justify-content: space-between;}When the number of items is less than four, here is what will happen.This isn’t good. There are different solutions to this:MarginFlexbox gap (Use with caution)Padding (Can be applied to the parent of each child element)Adding empty elements to act as a spacer.For simplicity, I will use gap..wrapper { display: flex; flex-wrap: wrap; gap: 1rem;}Examples and use cases # Toggle Defensive let defensiveToggle02 = document.querySelector("#test2"); defensiveToggle02.addEventListener('click', function () { if
2年前
記事のアイキャッチ画像
Scrollbars on demand
Defensive CSS
Luckily, we can control to show a scrollbar or not only in the case of having a long content. That being said, it’s highly recommended to use auto as a value for overflow.Consider the following example.Notice how even if the content is short, there is a scrollbar visible. This isn’t good for a UI. As a designer, it’s just confusing to see a scrollbar when it’s not needed..element { overflow-y: auto;}With overflow-y: auto, the scrollbar will only be visible if the content is long. Otherwise, it won’t be there. Here is an updated figure.Examples and use cases #For macOS users, the scrollbar is shown only on scroll. You can change that in the settings via "System Preferences -> General -> Show scroll bars -> Always".Lorem ipsum, dolor sit amet consectetur adipisicing elit. Doloremque harum est eum aperiam sed ut soluta quia nisi asperiores autem. Corporis consectetur amet sapiente necessitatibus veritatis aut quia maiores. Enim.Title Lorem ipsum dolor sit amet consectetur adipisicing elit
2年前
記事のアイキャッチ画像
Scrollbar gutter
Defensive CSS
Another thing that is related to scrolling is the scrollbar gutter. Taking the previous example, when the content gets longer, adding a scrollbar will cause a layout shift. The reason the layout shift happens is to reserve a space for the scrollbar.Consider the following figure.Notice how the content shifted when it became longer as a result of showing a scrollbar. We can avoid that behavior by using the scrollbar-gutter property..element { scrollbar-gutter: stable;}Checkout the following interactive demo on Codepen.See the Pen Scrollbar Gutter - Demo by Ahmad Shadeed (@shadeed) on CodePen.Related articles #Custom Scrollbars In CSS
2年前
記事のアイキャッチ画像
Scroll chaining
Defensive CSS
Have you ever opened a modal and started scrolling, and then when you reach the end and keep scrolling, the content underneath the modal (the body element) will scroll? This is called scroll chaining.There have been a few hacks to make this work in the past years, but now, we can do that with CSS only, thanks to the ‌overscroll-behavior CSS property.In the following figure, you see the default scroll chaining behavior.To avoid that ahead of time, we can add that to any component that needs to scroll (e.g: chat component, mobile menu.. etc). The nice thing about this property is that it won’t have an effect until there is scrolling..modal__content { overscroll-behavior-y: contain; overflow-y: auto;}Examples and use cases #Scroll chaining #In this example, try to scroll the modal and you will notice that the body scrolled once you view all the modal content. This is called scroll chaining.Thankfully, we can solve now with CSS. Try to toggle the checkbox and try scrolling again!.modal__bo
2年前
記事のアイキャッチ画像
Position sticky with CSS Grid
Defensive CSS
Have you ever tried using position: sticky with a child of a grid container? The default behavior for grid items is to stretch. As a result, the aside element in the example below is equal to the main section height.To make it work as expected, you need to reset align-self property.aside { align-self: start; position: sticky; top: 1rem;}I wrote about that topic in detail on my blog, if you're interested.Examples and use cases #Main and Aside #TitleLorem ipsum dolor sit amet consectetur adipisicing elit. Saepe corporis iure, quo asperiores sequi aspernatur odio adipisci, eligendi, unde provident mollitia sed deleniti! Nisi, facilis hic voluptatibus excepturi autem sequi!TitleLorem ipsum dolor sit amet consectetur adipisicing elit. Saepe corporis iure, quo asperiores sequi aspernatur odio.AsideToggle Defensive let defensiveToggle02 = document.querySelector("#test2"); defensiveToggle02.addEventListener('click', function () { if (defensiveToggle02.checked) { document .querySelector('.page_
2年前
記事のアイキャッチ画像
Image maximum width
Defensive CSS
As a general rule them, don't forget to set max-width: 100% to all images. This can be added to the CSS reset that you use.img { max-width: 100%; object-fit: cover;}Examples and use cases #Image #Toggle Defensive let defensiveToggle02 = document.querySelector("#test2"); defensiveToggle02.addEventListener('click', function () { if (defensiveToggle02.checked) { document .querySelector('.demo-img') .style .maxWidth = "100%"; } else { document .querySelector('.demo-img') .style .maxWidth = "initial"; } });
2年前
記事のアイキャッチ画像
Grouping vendor selectors
Defensive CSS
It's not recommended to group selectors that are meant to work with different browsers. For example, styling an input's placeholder needs multiple selectors per the browser. If we group the selectors, the entire rule will be invalid, according to w3c./* Don't do this, please */input::-webkit-input-placeholder,input:-moz-placeholder { color: #222;}Instead, do this.input::-webkit-input-placeholder { color: #222;}input:-moz-placeholder { color: #222;}
2年前
記事のアイキャッチ画像
Minimum Content Size In CSS grid
Defensive CSS
Similar to flexbox, CSS grid has a default minimum content size for its child items which is auto. That means, if there is an element that is larger than the grid item, it will overflow.In the example above, we have a carousel within the main section. For context, here is the HTML and CSS.<div class="wrapper"> <main> <section class="carousel"></section> </main> <aside></aside></div>@media (min-width: 1020px) { .wrapper { display: grid; grid-template-columns: 1fr 248px; grid-gap: 40px; }}.carousel { display: flex; overflow-x: auto;}Since the carousel is a flex container that doesn’t wrap, its width is larger than the main section, and thus the grid item respected that. As a result, there is horizontal scrolling.To fix that, we have three different solutions:Using minmax()Applying min-width to the grid itemAdding overflow: hidden to the grid itemAs a defensive CSS mechanism, it doesn't matter which solution to pick, but for this example I will go with the min-width one.I wrote about that
2年前
記事のアイキャッチ画像
Minimum Content Size In CSS Flexbox
Defensive CSS
If a flex item has a text element or an image that is bigger than the item itself, the browser won’t shrink them. That is the default behavior for flexbox.Consider the following example..card { display: flex;}Even if we use overflow-wrap: break-word, it won’t work..card__title { overflow-wrap: break-word;}To change that default behavior, we need to set the min-width of the flex item to 0. That’s because the min-width default value is auto, the overflow happens..card__title { overflow-wrap: break-word; min-width: 0;}The same thing applies to a column flex wrapper, but we will use min-height: 0 instead.Examples and use cases #Long card title #In this example, the card title is too long. As a result, flexbox will apply the default behaviour which is to make the element size as equal to its content length.That's because the default min-width value is auto. This is a sampletitlethatisabiglongToggle Defensive To fix that, we need to apply min-width: 0 on the flex item. Try to check the "Togg
2年前
記事のアイキャッチ画像
Fixed sizes
Defensive CSS
One of the common things that break a layout is using a fixed width or height with an element that has content in different lengths.The fixed height #I often see a hero section with a fixed height and content that is larger than that height, which results in a broken layout. Not sure how that looks? Here it is..hero { height: 350px;}To avoid the content leaking out of the hero, we need to use min-height instead of height.That way, if the content gets larger, the layout won’t break.The fixed width #Have you ever seen a button that has its label too close to the left and right edges? This is due to using a fixed width..button { width: 100px;}If the button’s label is longer than 100px, it will be close to the edges. If it’s too long, the text will leak out of it. This isn’t good!To fix that, we can simply replace width with min-width.Examples and use cases #A Fixed-height hero section #Lorem ipsum dolor sit amet consectetur adipisicing elit. Veritatis maxime aspernatur fugiat animi? Quae
2年前
記事のアイキャッチ画像
CSS Variable Fallback
Defensive CSS
CSS variables are gaining more and more usage in web design. There is a method that we can apply to use them in a way that doesn’t break the experience, in case the CSS variable value was empty for some reason.This is particularly useful when feeding the value of a CSS variable via Javascript. Here is an example:.message__bubble { max-width: calc(100% - var(--actions-width));}The variable --actions-width is being used within the calc() function and its value is coming from Javascript. Let’s suppose that Javascript failed for some reason, what will happen? The max-width will compute to none.We can avoid that ahead of time and add a fallback value to the var()..message__bubble { max-width: calc(100% - var(--actions-width, 70px));}That way, if the variable isn’t defined, the fallback 70px will be used. This approach can be used in case there is a possibility that the variable might fail (e.g: coming from Javascript).Examples and use cases #An invalid CSS variable #.element { background-co
2年前
記事のアイキャッチ画像
CSS grid fixed values
Defensive CSS
Say we have a grid that contains an aside and main. The CSS looks like this:.wrapper { display: grid; grid-template-columns: 250px 1fr; gap: 1rem;}This will break on small viewport sizes due to the lack of space. To avoid such an issue, always use a media query when using CSS grid like the above.@media (min-width: 600px) { .wrapper { display: grid; grid-template-columns: 250px 1fr; gap: 1rem; }}Examples and use cases #Main and aside #In the following demo, try to resize the handler, you will notice that the grid will keep the two columns even though the space isn't enough. This will result in a horizontal scrolling.When the "Toggle Defensive" is active, it will only add the grid if the viewport size is larger at a specific point.Main sectionAside with fixed widthToggle Defensive let defensiveToggle02 = document.querySelector("#test2"); defensiveToggle02.addEventListener('click', function () { if (defensiveToggle02.checked) { document .querySelector('.wrapper') .style .setProperty('--co
2年前
記事のアイキャッチ画像
Background repeat
Defensive CSS
Oftentimes, when using a large image as a background, we tend to forget to account for the case when the design is viewed on a large screen. That background will repeat by default.This mostly won’t be visible on a laptop screen, but it can be seen clearly on larger screens.To avoid that behavior in advance, make sure to reset background-repeat..hero { background-image: url('..'); background-repeat: no-repeat;}Examples and use cases #Section background # Toggle Defensive let defensiveToggle02 = document.querySelector("#test2"); defensiveToggle02.addEventListener('click', function () { if (defensiveToggle02.checked) { document .querySelector('.hero') .style .setProperty('--bg-repeat', 'no-repeat'); } else { document .querySelector('.hero') .style .setProperty('--bg-repeat', 'repeat'); } });
2年前
記事のアイキャッチ画像
Auto-fit Vs Auto-fill
Defensive CSS
When using CSS grid minmax() function, it's important to decide between using the auto-fit or auto-fill keywords. When used incorrectly, it can lead to unexpected results.When using minmax() function, the auto-fit keyword will expand the grid items to fill the available space. While auto-fill will keep the available space reserved without altering the grid items width.That being said, using auto-fit might lead to grid items being too wide, especially when they are less than expected. Consider the following example..wrapper { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-gap: 1rem;}If there is only one grid item and auto-fit is used, the item will expand to fill the container width.Most of the time, such behavior isn't needed, so using auto-fill is better in my opinion..wrapper { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-gap: 1rem;}Examples and use cases #Spacing #.wrapper { --sizing: auto-fit; display: grid; gri
2年前
記事のアイキャッチ画像
Component Spacing
Defensive CSS
We developers need to account for different content lengths. That means, spacing should be added to a component, even though it seems like not needed.In this example, we have a section title and an action button on the right side. Currently, it looks okay. But let’s see what happens when the title is longer.Notice how the text is too close to the action button? You might be thinking about multi-line wrapping, but I will come to that in another section. For now, let’s focus on the spacing.If the title has spacing and text truncation, we won’t see such an issue..section__title { margin-right: 1rem;}Examples and use cases #Spacing #This is similar to the long content tip, but in that case we don't want to truncate the text since the user must read it all. One little thing here is to account for spacing even though the content we've currently isn't long.Consider the following demo. We have a short title and everything works like a charm.A little short title More Toggle Defensive However, w
2年前
記事のアイキャッチ画像
Long Content
Defensive CSS
Here is a list of people’s names and it looks perfect for now.However, since this is user-generated content, we need to be careful about how to defense the layout in case of something too long.See the following figure:In such layouts, consistency is important. To achieve that, we can simply truncate the name by using text-overflow and its friends..username { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}Examples and use cases #Section header #In some scenarios, we might need to truncate a text that isn't important for the user, or doesn't affect the user experience. In such a case, truncating the text is a good idea.Defensive CSS is a good way to write bullet-proof CSS!Toggle Defensive If you’re interested to sharpen your skills in handling long content in CSS, I wrote a detailed article on that topic.
2年前
記事のアイキャッチ画像
Image Distortion
Defensive CSS
When we don’t have control over an image’s aspect ratio on a web page, it’s better to think ahead and provide a solution when a user uploads an image that isn’t aligned with the aspect ratio.In the following example, we have a card component with a photo. It looks good.When the user uploads an image of a different size, it will be stretched. This isn’t good. Look at how the image is stretched!The simplest fix for that is to use CSS object-fit..card__thumb { object-fit: cover;}img { object-fit: cover;}Examples and use cases #Image component #Toggle Defensive Learn more about object-fit in this article on Smashing Magazine.
2年前
記事のアイキャッチ画像
Flexbox Wrapping
Defensive CSS
CSS flexbox is one of the most useful CSS layout features nowadays. It’s tempting to add display: flex to a wrapper and have the child items ordered next to each other.The thing is when there is not enough space, those child items won’t wrap into a new line by default. We need to change that behavior with flex-wrap: wrap.Here is a typical example. We have a group of options that should be displayed next to each other..options-list { display: flex;}When there is less space, horizontal scrolling will occur. That’s should be expected and isn’t actually a “problem”.Notice how the items are still next to each other. To fix that, we need to allow flex wrapping..options-list { display: flex; flex-wrap: wrap;}Examples and use cases #Breadcrumbs #Home Articles Defensive CSS Toggle Defensive let defensiveToggle01 = document.querySelector("#test"); defensiveToggle01.addEventListener('click', function () { if (defensiveToggle01.checked) { document .querySelector('.breadcrumbs') .style .setProperty
2年前