From dae79b658f5ac8940cdac9f7e0ecec8cfb4ba6fe Mon Sep 17 00:00:00 2001 From: Web Dev Simplified Date: Tue, 2 Jul 2024 13:03:44 -0500 Subject: [PATCH 1/3] Getting Started --- .../2024-07/css-focus-crash-course/index.mdx | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/pages/2024-07/css-focus-crash-course/index.mdx diff --git a/src/pages/2024-07/css-focus-crash-course/index.mdx b/src/pages/2024-07/css-focus-crash-course/index.mdx new file mode 100644 index 0000000..7086241 --- /dev/null +++ b/src/pages/2024-07/css-focus-crash-course/index.mdx @@ -0,0 +1,108 @@ +--- +layout: "@layouts/BlogPost.astro" +title: "We Can Finally Animate height: auto; in CSS!" +date: "2024-07-01" +description: "For decades, height: auto; has been a pain to animate in CSS. But with the introduction of the calc-size() function in CSS, we can finally animate height: auto; without the need for any JavaScript." +tags: ["CSS"] +--- + +import CalcSizeComparison from "@blogComponents/cssCalcSize/CalcSizeComparison.astro" + +## Introduction + +Animating `height: auto;` in CSS seems like it should be easy, but CSS is unable to animate to/from `height: auto;` since it needs a specific height value to run any animation/transition. This has been a pain point for web developers for decades, and the only way to animate `height: auto;` was to use JavaScript to calculate the height of the element and then animate it. This is obviously not ideal, which is why CSS has finally added the brand new `calc-size()` function which makes this type of animation trivial. + +## `calc-size()` + +The `calc-size()` function works exactly the same as the `calc()` function, but it has the additional capability of calculating based on sizes that are automatically calculated by the browser. These values are: + + 1. `auto` + 2. `min-content` + 3. `max-content` + 4. `fit-content` + 5. `stretch` + 6. `contain` + +Essentially, what this function does is convert values like `auto` to specific pixel values which it can then use in calculations with other values. This is handy on its own, but where it is most useful is with animating elements that are `auto` sized. + +```css {8} +.element { + height: 0; + overflow: hidden; + transition: height 0.3s; +} + +.element.open { + height: calc-size(auto); +} +``` + +By wrapping our `auto` value in the `calc-size()` function, we can now animate the height of the element from `0` to `auto` without any JavaScript. Here is an example of what this looks like in practice: + + + +The only thing you need to be away of is that you cannot animate between two automatically calculated values, such as `auto` and `min-content`. + +Another interesting thing about `calc-size()` is you can actually use it on the non-automatic value in the animation and it will still animate correctly. As long as you have `calc-size` on one of the values in the animation, it will work. + +```css {3} +.element { + /* This still works */ + height: calc-size(0px); + overflow: hidden; + transition: height 0.3s; +} + +.element.open { + height: auto; +} +``` + +### Doing Actual Calculations + +By far the most common use for this will be with animations/transitions as shown above, but since this function works just like `calc` it can actually be used to do certain calculations that used to be impossible. + +```css +.element { + width: calc-size(min-content, size + 50px); +} +``` + +The above CSS will set the width of the element to the minimum content size plus `50px`. The syntax for this is a bit confusing so let me explain. + +`calc-size` takes two arguments, the first is the size that you want to calculate, and the second is the calculation you want to perform. In this case, we are calculating the `min-content` size of the element and then adding `50px` to that value. The keyword `size` is always used to represent the current size of the first property passed to `calc-size`. This means in our example `size` would be equal to the `min-content` size of the element. + +You can even nest multiple `calc-size` functions to perform more complex calculations. + +```css +.element { + width: calc-size(calc-size(min-content, size + 50px), size * 2); +} +``` + +This will calculate the `min-content` size of the element, add `50px` to that value, and then multiply the result by `2`. + +## Browser Support + +This is where we get to the bad news. As of writing this article, `calc-size()` is only supported in Chrome Canary when the `#enable-experimental-web-platform-features` flag is enabled. It is so new there isn't even a caniuse.com page for me to link to yet. + +Luckily, this CSS feature is not something that will break your site if it isn't supported. It will just mean that the animation won't work, so you can use it today and it will act as a progressive enhancement for users on browsers that support it. + +```css {8-9} +.element { + height: 0; + overflow: hidden; + transition: height 0.3s; +} + +.element.open { + height: auto; + height: calc-size(auto); +} +``` + +With the above CSS the animation will work in browsers that support `calc-size()` while in older browsers it will just show the element without any animation. + +## Conclusion + +`calc-size()` is a fantastic new addition to CSS that will make animating `auto` based sizes incredibly easy. It also opens up a lot of possibilities for doing calculations that were previously impossible in CSS. I can't wait for this feature to be supported in all browsers! From 28f2cb3b82ac8d52a7c4491daa65762e1f08ee16 Mon Sep 17 00:00:00 2001 From: Web Dev Simplified Date: Wed, 3 Jul 2024 08:04:48 -0500 Subject: [PATCH 2/3] Add CSS Focus Article --- .../cssFocusCrashCourse/CssFocusButton.astro | 24 +++ .../CssFocusContainer.astro | 27 +++ .../cssFocusCrashCourse/CssFocusInput.astro | 25 +++ src/components/BlogPost.astro | 19 ++- src/components/BlogPostPreview.jsx | 13 +- src/layouts/BlogPost.astro | 3 +- src/pages/2022-09/css-has-selector/index.mdx | 3 +- .../2024-07/css-focus-crash-course/index.mdx | 157 +++++++++++------- src/pages/index.astro | 4 + src/styles/theme.css | 1 + 10 files changed, 213 insertions(+), 63 deletions(-) create mode 100644 src/blogComponents/cssFocusCrashCourse/CssFocusButton.astro create mode 100644 src/blogComponents/cssFocusCrashCourse/CssFocusContainer.astro create mode 100644 src/blogComponents/cssFocusCrashCourse/CssFocusInput.astro diff --git a/src/blogComponents/cssFocusCrashCourse/CssFocusButton.astro b/src/blogComponents/cssFocusCrashCourse/CssFocusButton.astro new file mode 100644 index 0000000..75c8408 --- /dev/null +++ b/src/blogComponents/cssFocusCrashCourse/CssFocusButton.astro @@ -0,0 +1,24 @@ +--- +const { focusType } = Astro.props +--- + + + + diff --git a/src/blogComponents/cssFocusCrashCourse/CssFocusContainer.astro b/src/blogComponents/cssFocusCrashCourse/CssFocusContainer.astro new file mode 100644 index 0000000..7c5cf3d --- /dev/null +++ b/src/blogComponents/cssFocusCrashCourse/CssFocusContainer.astro @@ -0,0 +1,27 @@ +--- +const { focusType } = Astro.props +--- + +
+ + diff --git a/src/blogComponents/cssFocusCrashCourse/CssFocusInput.astro b/src/blogComponents/cssFocusCrashCourse/CssFocusInput.astro new file mode 100644 index 0000000..d4b0b9d --- /dev/null +++ b/src/blogComponents/cssFocusCrashCourse/CssFocusInput.astro @@ -0,0 +1,25 @@ +--- +const { focusType, placeholder } = Astro.props +--- + + + + diff --git a/src/components/BlogPost.astro b/src/components/BlogPost.astro index 4920f2a..8761e0d 100644 --- a/src/components/BlogPost.astro +++ b/src/components/BlogPost.astro @@ -6,11 +6,12 @@ import ShareButtons from "/src/components/ShareButtons.jsx" export interface Props { title: string date: string + updatedDate: string url: string tags: string[] } -const { title, date, tags, url } = Astro.props +const { title, date, updatedDate, tags, url } = Astro.props const siteUrl = Astro.site.href --- @@ -19,7 +20,16 @@ const siteUrl = Astro.site.href

{title}

-

{dateFormatter.format(new Date(date))}

+

+ {dateFormatter.format(new Date(date))} +

+ { + updatedDate && ( +

+ Updated: {dateFormatter.format(new Date(updatedDate))} +

+ ) + }
{ @@ -75,6 +85,11 @@ const siteUrl = Astro.site.href color: var(--theme-text-lighter); } + .publish-date.old-date { + font-size: 0.9rem; + text-decoration: line-through; + } + .title { font-size: 2.25rem; font-weight: 700; diff --git a/src/components/BlogPostPreview.jsx b/src/components/BlogPostPreview.jsx index e48c75d..226ba40 100644 --- a/src/components/BlogPostPreview.jsx +++ b/src/components/BlogPostPreview.jsx @@ -24,9 +24,20 @@ export default function BlogPostPreview({ post }) { margin: 0, fontSize: "1rem", color: "var(--theme-text-lighter)", + display: "flex", + flexDirection: "column", }} > - {dateFormatter.format(post.date)} + + {dateFormatter.format(post.date)} + + {post.updatedDate && ( + <>Updated: {dateFormatter.format(post.updatedDate)} + )}

- +
diff --git a/src/pages/2022-09/css-has-selector/index.mdx b/src/pages/2022-09/css-has-selector/index.mdx index cc42498..3bf7b8f 100644 --- a/src/pages/2022-09/css-has-selector/index.mdx +++ b/src/pages/2022-09/css-has-selector/index.mdx @@ -2,6 +2,7 @@ layout: "@layouts/BlogPost.astro" title: "CSS :has A Parent Selector And It Is Better Than I Ever Could Have Hoped For" date: "2022-09-26" +updatedDate: "2024-07-29" description: "CSS has been lacking a parent selector for years and now they finally introduced one with the :has pseudo class and it is incredible!" tags: ["CSS"] --- @@ -115,7 +116,7 @@ This selector has an overall specificity of 2 classes and 1 element since the fi ## Browser Support -With every cool CSS feature you always have to consider browser support, but luckily the browser support for this new property is quite good. It may seem low considering it only has [56.2% support](https://caniuse.com/css-has), but this is a bit misleading since every major browser other than Firefox has support for this feature. The reason for the low percentage is because this features just launched at the end of August 2022 in Chrome so people are still in the process of updating Chrome to the latest versions. Firefox also has this feature behind a feature flag which hopefully means it is coming to the browser very soon. In just a few months this feature should be above 90% and able to be used on pretty much every site. +The `:has` selector is supported in all major browsers as of the end of 2023. ## Conclusion diff --git a/src/pages/2024-07/css-focus-crash-course/index.mdx b/src/pages/2024-07/css-focus-crash-course/index.mdx index 7086241..086b8fe 100644 --- a/src/pages/2024-07/css-focus-crash-course/index.mdx +++ b/src/pages/2024-07/css-focus-crash-course/index.mdx @@ -1,108 +1,149 @@ --- layout: "@layouts/BlogPost.astro" -title: "We Can Finally Animate height: auto; in CSS!" -date: "2024-07-01" -description: "For decades, height: auto; has been a pain to animate in CSS. But with the introduction of the calc-size() function in CSS, we can finally animate height: auto; without the need for any JavaScript." +title: "Do You Know All 4 CSS Focus Styles?" +date: "2024-07-29" +description: "Handling focus in CSS is quite a bit more complicated than it seems and in this article I cover the 3 main CSS focus methods as well as a secret fourth focus method." tags: ["CSS"] --- -import CalcSizeComparison from "@blogComponents/cssCalcSize/CalcSizeComparison.astro" +import CSSFocusButton from "@blogComponents/cssFocusCrashCourse/CssFocusButton.astro" +import CSSFocusInput from "@blogComponents/cssFocusCrashCourse/CSSFocusInput.astro" +import CSSFocusContainer from "@blogComponents/cssFocusCrashCourse/CSSFocusContainer.astro" +import Tangent from "@blogComponents/lib/Tangent.astro" ## Introduction -Animating `height: auto;` in CSS seems like it should be easy, but CSS is unable to animate to/from `height: auto;` since it needs a specific height value to run any animation/transition. This has been a pain point for web developers for decades, and the only way to animate `height: auto;` was to use JavaScript to calculate the height of the element and then animate it. This is obviously not ideal, which is why CSS has finally added the brand new `calc-size()` function which makes this type of animation trivial. +It may seem strange to write an entire article on handling focus in CSS since it is a pretty simple property at first glance. That is until you realize that CSS actually has 3 (plus one secret) focus methods that all behave differently. These properties even have different behaviors depending on what elements you use them on which makes it even more confusing. In this article I will be breaking down all 4 focus methods, showing you when to use each one, and how they differ from each other. -## `calc-size()` +## What Is Focus? -The `calc-size()` function works exactly the same as the `calc()` function, but it has the additional capability of calculating based on sizes that are automatically calculated by the browser. These values are: +Before we dive into each focus method we need to understand what a focus state is. When an element is focused it means that it is currently selected by the user. This can happen when a user clicks on an element, uses the tab key to navigate through a page, or even when a user interacts with an element using a screen reader. - 1. `auto` - 2. `min-content` - 3. `max-content` - 4. `fit-content` - 5. `stretch` - 6. `contain` +The focus state is important for accessibility reasons as it helps users understand where they are on a page and what they are interacting with. Imagine trying to fill out a form and not knowing which input you are currently typing in. This is what the web would be like if there were no focus states. -Essentially, what this function does is convert values like `auto` to specific pixel values which it can then use in calculations with other values. This is handy on its own, but where it is most useful is with animating elements that are `auto` sized. +## `:focus` -```css {8} -.element { - height: 0; - overflow: hidden; - transition: height 0.3s; +The main focus method is the `:focus` pseudo-class. This is the what most people think of when they think of focus styles in CSS, but this pseudo-class has quite a few problems which is why I almost never use it. + +```css {5} +button { + background-color: blue; } -.element.open { - height: calc-size(auto); +button:focus { + background-color: red; } ``` -By wrapping our `auto` value in the `calc-size()` function, we can now animate the height of the element from `0` to `auto` without any JavaScript. Here is an example of what this looks like in practice: +The styles within the `:focus` pseudo-class will be applied whenever an element is focused. It doesn't matter how the element was focused (clicked on, keyboard navigation, screen readers, etc.) it will always show the focus styles. You can see this in action in the example below. + +Focus Me - +As you can see it doesn't matter how you focus this button it will always turn red. This is generally not ideal since when you click on a button you don't generally want it to show focus styles after you have finished clicking on it, but you do want the focus state to persist when using keyboard navigation since this focus state tells the user where they are on the page. -The only thing you need to be away of is that you cannot animate between two automatically calculated values, such as `auto` and `min-content`. + + If you are having trouble focusing on the button with the keyboard try + clicking on the text just above the button and then clicking the + Tab key. + -Another interesting thing about `calc-size()` is you can actually use it on the non-automatic value in the animation and it will still animate correctly. As long as you have `calc-size` on one of the values in the animation, it will work. +## `:focus-visible` -```css {3} -.element { - /* This still works */ - height: calc-size(0px); - overflow: hidden; - transition: height 0.3s; +I mentioned how `:focus` is not always ideal since it shows the focus styles no matter how the element was focused. This is where the `:focus-visible` pseudo-class comes in. This pseudo-class is a bit smarter than `:focus` since it only shows the focus styles when the browser deems the user needs those styles to know where they are on the page and what element they currently have focused. I almost always use `:focus-visible` over `:focus` since it provides a better user experience. + +```css {5} +button { + background-color: blue; } -.element.open { - height: auto; +button:focus-visible { + background-color: red; } ``` -### Doing Actual Calculations + + Focus Me With Keyboard + + +As you can see the above button above does not show any focus styles when it is clicked, but if you use the keyboard to navigate to this element it will show the focus styles. -By far the most common use for this will be with animations/transitions as shown above, but since this function works just like `calc` it can actually be used to do certain calculations that used to be impossible. + + If you look closely you may notice that there is a ring that appears around + the button when you use keyboard navigation to focus it. This is part of the + default browser styles and is called the `outline` property. The browser + applies this `outline` property using the `:focus-within` pseudo-class which + is why it only appears when needed to assist the user in knowing where they + are on the page. + + +One thing to note about `:focus-visible` is that it works differently when used on different elements. For example, if you use `:focus-visible` on an input element it will show the focus style no matter what. It doesn't matter if you click the input or use keyboard navigation it will always show the focus styles. This is because the browser deems it important to show the focus styles on input elements no matter how they are focused. + +```css {5} +input { + border-color: blue; +} -```css -.element { - width: calc-size(min-content, size + 50px); +input:focus-visible { + border-color: red; } ``` -The above CSS will set the width of the element to the minimum content size plus `50px`. The syntax for this is a bit confusing so let me explain. + -`calc-size` takes two arguments, the first is the size that you want to calculate, and the second is the calculation you want to perform. In this case, we are calculating the `min-content` size of the element and then adding `50px` to that value. The keyword `size` is always used to represent the current size of the first property passed to `calc-size`. This means in our example `size` would be equal to the `min-content` size of the element. +## `:focus-within` -You can even nest multiple `calc-size` functions to perform more complex calculations. +The `:focus-within` pseudo-class is a bit different than the previous two focus methods. This pseudo-class is used to apply styles to a parent element based on the `:focus` state of its children. If any of the children would show the `:focus` state then the parent will also show the `:focus-within` state. -```css -.element { - width: calc-size(calc-size(min-content, size + 50px), size * 2); +```css {5} +.container { + border-color: blue; +} + +.container:focus-within { + border-color: red; } ``` -This will calculate the `min-content` size of the element, add `50px` to that value, and then multiply the result by `2`. + + :focus + :focus-visible + No Focus Styles + + +In the above example there is container that will show a red border anytime one of its children is focused. There are 3 buttons in that container. The first has a `:focus` style, the second has a `:focus-visible` style, and the third has no focus styles. As you can see it doesn't matter which button you click the container will always show the `:focus-within` styles since one of its children is focused. -## Browser Support +It is important to know that the child elements do not need to have a focus style defined for the parent to show the `:focus-within` state. The parent will show the `:focus-within` state as long as one of its children is focused. This works with any element that can be focused, not just buttons. -This is where we get to the bad news. As of writing this article, `calc-size()` is only supported in Chrome Canary when the `#enable-experimental-web-platform-features` flag is enabled. It is so new there isn't even a caniuse.com page for me to link to yet. +## The Secret Fourth Focus Method -Luckily, this CSS feature is not something that will break your site if it isn't supported. It will just mean that the animation won't work, so you can use it today and it will act as a progressive enhancement for users on browsers that support it. +The final focus method is a bit different since there is no built in CSS pseudo class for `:focus-visible-within`. Instead we have to write our own custom CSS selector that does the same thing. This custom selector combines hwo `:focus-within` and `:focus-visible` work to create a focus method that only shows focus styles when a child of the element would have its `:focus-visible` styles shown. -```css {8-9} -.element { - height: 0; - overflow: hidden; - transition: height 0.3s; +```css {5} +.container { + border-color: blue; } -.element.open { - height: auto; - height: calc-size(auto); +.container:has(:focus-visible) { + border-color: red; } ``` -With the above CSS the animation will work in browsers that support `calc-size()` while in older browsers it will just show the element without any animation. + + :focus + :focus-visible + No Focus Styles + + + +I have pretty much the same code in this example as the previous example, but I added a single input element to the container. This input element has a `:focus` style applied to it. This container will only show the custom `:focus-visible-within` styles when I use the keyboard to navigate to any of the buttons or when I focus the input in any way. If I click on any of the buttons the container will not show the custom `:focus-visible-within` styles. + +The way this `:focus-visible-within` works is by using the `:has` pseudo element to only select the `.container` if at least one child element has the `:focus-visible` pseudo-class applied to it. + + + If you want to learn more about the `:has` selector and why I love it you can + check out my complete [:has selector article](/2022-09/css-has-selector). + ## Conclusion -`calc-size()` is a fantastic new addition to CSS that will make animating `auto` based sizes incredibly easy. It also opens up a lot of possibilities for doing calculations that were previously impossible in CSS. I can't wait for this feature to be supported in all browsers! +Hopefully this article has helped you understand how each focus method differs from the others and when each should be used. Personally, I almost never use `:focus` as `:focus-visible` is almost always a better user experience. I don't tend to use `:focus-within` or the custom `:focus-visible-within` as much, but when you need them they are incredibly useful. diff --git a/src/pages/index.astro b/src/pages/index.astro index f1cf0d1..014525a 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -8,6 +8,7 @@ import BaseTopOfBody from "src/components/BaseTopOfBody.astro" interface MarkdownFrontmatter { date: number + updatedDate?: number title: string description: string tags: string[] @@ -30,6 +31,9 @@ const allPosts = allMarkdownPosts description: post.frontmatter.description, tags: post.frontmatter.tags, date: new Date(post.frontmatter.date), + updatedDate: post.frontmatter.updatedDate + ? new Date(post.frontmatter.updatedDate) + : undefined, url: post.url, })) .sort((a, b) => b.date.valueOf() - a.date.valueOf()) diff --git a/src/styles/theme.css b/src/styles/theme.css index 76d4d81..9db7c0b 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -87,6 +87,7 @@ --theme-popup-bg: var(--color-gray-700); --theme-tangent-bg: var(--color-gray-700); --theme-tangent-border: var(--color-gray-600); + --theme-tangent-code-inline-bg: var(--color-gray-500); --theme-red: var(--color-red-dark); --theme-blue: var(--color-blue-dark); From ff0fb3f3da520e813181dc88c32a839f9dd5d18d Mon Sep 17 00:00:00 2001 From: Web Dev Simplified Date: Wed, 3 Jul 2024 08:25:15 -0500 Subject: [PATCH 3/3] Fix Imports --- src/pages/2024-07/css-focus-crash-course/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/2024-07/css-focus-crash-course/index.mdx b/src/pages/2024-07/css-focus-crash-course/index.mdx index 086b8fe..2828c83 100644 --- a/src/pages/2024-07/css-focus-crash-course/index.mdx +++ b/src/pages/2024-07/css-focus-crash-course/index.mdx @@ -7,8 +7,8 @@ tags: ["CSS"] --- import CSSFocusButton from "@blogComponents/cssFocusCrashCourse/CssFocusButton.astro" -import CSSFocusInput from "@blogComponents/cssFocusCrashCourse/CSSFocusInput.astro" -import CSSFocusContainer from "@blogComponents/cssFocusCrashCourse/CSSFocusContainer.astro" +import CSSFocusInput from "@blogComponents/cssFocusCrashCourse/CssFocusInput.astro" +import CSSFocusContainer from "@blogComponents/cssFocusCrashCourse/CssFocusContainer.astro" import Tangent from "@blogComponents/lib/Tangent.astro" ## Introduction