89 | {% endset %}
90 |
91 | {% set resourceLink %}
92 |
93 | [Learn more about the `:target` pseudo class](https://moderncss.dev/guide-to-advanced-css-selectors-part-two/) in part two of my guide to advanced CSS selectors.
94 |
95 | {% endset %}
96 |
97 | {% set codepenForm %}
98 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
99 | {% endset %}
100 |
101 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-aspect-ratio-gallery.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Aspect Ratio Gallery"
4 | categories: ["flexbox", "layout", "component"]
5 | date: 2021-02-14
6 | templateEngineOverride: md, njk
7 | ---
8 |
9 | {% set description %}
10 |
11 | The `aspect-ratio` property [has support in all major modern browsers](https://caniuse.com/mdn-css_properties_aspect-ratio), and by combining it with `object-fit` and flexbox, we can create a smol responsive gallery. Check out the CSS via your browser Inspector to modify the CSS custom properties on `.smol-aspect-ratio-gallery` and see how they affect this layout.
12 |
13 | The demo initially sets a height as a fallback for browsers that do not yet support `aspect-ratio`, and then uses `@supports` to upgrade to use of `aspect-ratio`.
14 |
15 | Note that `aspect-ratio` isn't just for images, but any element!
16 |
17 | > This solution also uses the [Smol Responsive Flexbox Grid](#smol-flexbox-grid).
18 |
19 | {% endset %}
20 |
21 | {% set css %}
22 | .smol-aspect-ratio-gallery {
23 | --min: 15rem;
24 | --aspect-ratio: 4/3;
25 | --gap: 0;
26 | }
27 |
28 | .smol-aspect-ratio-gallery li {
29 | height: max(25vh, 15rem);
30 | }
31 |
32 | @supports (aspect-ratio: 1) {
33 | .smol-aspect-ratio-gallery li {
34 | aspect-ratio: var(--aspect-ratio);
35 | height: auto;
36 | }
37 | }
38 |
39 | .smol-aspect-ratio-gallery img {
40 | display: block;
41 | object-fit: cover;
42 | width: 100%;
43 | height: 100%;
44 | }
45 | {% endset %}
46 |
47 | {% set html %}
48 |
49 |
50 |
54 |
55 |
56 |
60 |
61 |
62 |
66 |
67 |
68 |
72 |
73 |
74 |
78 |
79 |
80 | {% endset %}
81 |
82 | {% set resourceLink %}
83 |
84 | For an alternate method to `aspect-ratio` on this solution, check out my [flexbox gallery egghead lesson](https://egghead.io/lessons/flexbox-create-an-automatically-responsive-flexbox-gallery?af=2s65ms). You may also enjoy my [egghead lesson on `object-fit`](https://egghead.io/lessons/css-apply-aspect-ratio-sizing-to-images-with-css-object-fit?af=2s65ms).
85 |
86 | {% endset %}
87 |
88 | {% set codepenForm %}
89 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html, 'flex' %}
90 | {% endset %}
91 |
92 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-avatar-list.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Avatar List Component"
4 | categories: ["grid", "layout", "component"]
5 | date: 2021-02-20
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set resize %}false{% endset %}
10 | {% set layout %}centered{% endset %}
11 |
12 | {% set description %}
13 |
14 | This smol component, which you may also know as a [facepile](https://indieweb.org/facepile), is possible due to the ability of CSS grid to easily create overlapping content. Paired with CSS custom properties and `calc()` we can make this a contextually resizable component.
15 |
16 | Based on devices capabilities, the grid columns are adjusted to slightly narrower than the `--avatar-size`. Since nothing inherent to CSS grid stops the content overflowing, it forces an overlap based on DOM order of the list items. To ensure perfect circle images, we first use the `--avatar-size` value to explicitly set the list item dimensions. Then by setting both width and height to `100%` on the `img` in addition to `object-fit: cover` and `border-radius: 50%`, we can be assured that regardless of actual image dimensions the contents will be forced into a circle appearance.
17 |
18 | **Bonus trick #1** is the use of layered `box-shadow` values that only set a _spread_ to create the appearance of borders without adding to the computed dimensions of the image. The spread values are set with `em` so that they are relative to the avatar size. And that works because we set the list's `font-size` to `--avatar-size`.
19 |
20 | **Bonus trick #2** is using the _general sibling combinator_ (`~`) so that on hover or `:focus-within` of an `li`, all linked images that follow animate over to reveal more of the hovered avatar. If the number of avatars will cause wrapping, you may want to choose a different effect such as changing the layering via `z-index`.
21 |
22 | 🔎 Pop open your browser devtools and experiment with changing the `--avatar-size` value!
23 |
24 | {% endset %}
25 |
26 | {% set css %}
27 | .smol-avatar-list {
28 | --avatar-size: 3rem;
29 | --avatar-count: 3;
30 |
31 | display: grid;
32 | /* Default to displaying most of the avatar to
33 | enable easier access on touch devices, ensuring
34 | the WCAG touch target size is met or exceeded */
35 | grid-template-columns: repeat(
36 | var(--avatar-count),
37 | max(44px, calc(var(--avatar-size) / 1.15))
38 | );
39 | /* `padding` matches added visual dimensions of
40 | the `box-shadow` to help create a more accurate
41 | computed component size */
42 | padding: 0.08em;
43 | font-size: var(--avatar-size);
44 | }
45 |
46 | @media (any-hover: hover) and (any-pointer: fine) {
47 | .smol-avatar-list {
48 | /* We create 1 extra cell to enable the computed
49 | width to match the final visual width */
50 | grid-template-columns: repeat(
51 | calc(var(--avatar-count) + 1),
52 | calc(var(--avatar-size) / 1.75)
53 | );
54 | }
55 | }
56 |
57 | .smol-avatar-list li {
58 | width: var(--avatar-size);
59 | height: var(--avatar-size);
60 | }
61 |
62 | .smol-avatar-list li:hover ~ li a,
63 | .smol-avatar-list li:focus-within ~ li a {
64 | transform: translateX(33%);
65 | }
66 |
67 | .smol-avatar-list img,
68 | .smol-avatar-list a {
69 | display: block;
70 | border-radius: 50%;
71 | }
72 |
73 | .smol-avatar-list a {
74 | transition: transform 180ms ease-in-out;
75 | }
76 |
77 | .smol-avatar-list img {
78 | width: 100%;
79 | height: 100%;
80 | object-fit: cover;
81 | background-color: #fff;
82 | box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
83 | }
84 |
85 | .smol-avatar-list a:focus {
86 | outline: 2px solid transparent;
87 | /* Double-layer trick to work for dark and light backgrounds */
88 | box-shadow: 0 0 0 0.08em #29344B, 0 0 0 0.12em white;
89 | }
90 | {% endset %}
91 |
92 | {% set html %}
93 |
94 |
95 |
96 |
97 |
98 | {% endset %}
99 |
100 | {% set resourceLink %}
101 |
102 | ICYMI earlier - check out my quick [video lesson on object-fit](https://egghead.io/lessons/css-apply-aspect-ratio-sizing-to-images-with-css-object-fit?af=2s65ms) to learn more about how it works. Or you might enjoy more [methods for creating CSS borders](https://moderncss.dev/the-3-css-methods-for-adding-element-borders/) or see an additional [example of using `:focus-within`](https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/). _Source images from [avataaars](https://getavataaars.com/)_.
103 |
104 | {% endset %}
105 |
106 | {% set codepenForm %}
107 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
108 | {% endset %}
109 |
110 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-background-picture.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Background Picture"
4 | categories: ["grid", "component"]
5 | date: 2022-01-05
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | We can reuse the Smol Stack to enable layering the `` element with content as a replacement for using `background-image`. This will let you get the performance benefits of modern image formats like WebP, as well as being able to define `alt` text for accessibility.
12 |
13 | If you haven't worked with the `picture` element before, note that you apply image-related CSS to the internal `img` element.
14 |
15 | To emulate `background-image` type of behavior, we use `object-fit: cover` just like we also used for the [image gallery](#smol-aspect-ratio-gallery).
16 |
17 | **Bonus technique**: improve text contrast of the overlaid content by applying `filter` to the `img`. With the filter, we reduce `brightness` to darken the image while also increasing the `saturate` value to recuperate some of the image vibrancy. These are set with custom properties so that you can easily modify them per-instance if needed to ensure the best contrast.
18 |
19 | > This solution also uses the [Smol Stack Layout](#smol-stack-layout).
20 |
21 | {% endset %}
22 |
23 | {% set css %}
24 | .smol-background-picture img {
25 | --background-img-brightness: 0.45;
26 | --background-img-saturate: 1.25;
27 |
28 | object-fit: cover;
29 | width: 100%;
30 | height: 100%;
31 | /* decrease brightness to improve text contrast */
32 | filter:
33 | brightness(var(--background-img-brightness))
34 | saturate(var(--background-img-saturate));
35 | }
36 |
37 | /* Necessary if not already within a reset */
38 | .smol-background-picture :is(img, picture) {
39 | display: block;
40 | }
41 |
42 | .smol-background-picture__content {
43 | /* ensure stacking context above the picture */
44 | position: relative;
45 | align-self: center;
46 | color: #fff;
47 | text-align: center;
48 | padding: 1rem;
49 | }
50 |
51 | .smol-background-picture__content p {
52 | font-size: clamp(1.35rem, 5vw, 1.75rem);
53 | line-height: 1.3;
54 | }
55 | {% endset %}
56 |
57 | {% set html %}
58 |
81 | {% endset %}
82 |
83 | {% set resourceLink %}
84 |
85 | Learn more about using `picture` in my overview of [handling responsive image display](https://12daysofweb.dev/2021/image-display-elements/).
86 |
87 | {% endset %}
88 |
89 | {% set codepenForm %}
90 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
91 | {% endset %}
92 |
93 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-breakout-grid.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Breakout Grid"
4 | categories: ["grid", "layout"]
5 | date: 2023-10-06
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | Setup a flexible grid layout with collapsing outer columns for easy placement of "breakout" elements.
12 |
13 | The first feature is the use of the `[x-start]`/`[x-end]` syntax to create named grid areas that span more than one column (also works for rows!). In this example, we create three columns where the `grid` area spans all three, and the `content` area is the center column.
14 |
15 | Next, we've set the outer column widths to `1fr` and those columns will collapse first to give the `minmax()` definition for the center column priority, meaning they will eventually compute to a width of `0`. Important to note is that we're only setting _row_ gap, otherwise any column gap would keep space propped open between the middle and outer columns. The `minmax()` definition is explained in the demo for the [Smol Responsive Grid](#smol-css-grid).
16 |
17 | Finally, we assign everything that doesn't have the special `.breakout` class to the `content` named area, and the `.breakout` to the `grid` named area. Be sure to resize the demo to see the outer columns collapse to contain the breakout!
18 |
19 | {% endset %}
20 |
21 | {% set css %}
22 | .smol-breakout-grid {
23 | --max-content-width: 50ch;
24 | --breakout-difference: 0.2;
25 |
26 | /* Compute total allowed grid width to `--breakout-difference`
27 | larger than content area */
28 | --breakout-grid-width: calc(
29 | var(--max-content-width) +
30 | (var(--max-content-width) * var(--breakout-difference))
31 | );
32 |
33 | display: grid;
34 | grid-template-columns:
35 | [grid-start] 1fr
36 | [content-start] minmax(
37 | min(100%, var(--max-content-width)),
38 | 1fr
39 | )
40 | [content-end] 1fr
41 | [grid-end];
42 | width: min(100% - 2rem, var(--breakout-grid-width));
43 | row-gap: 1rem;
44 | margin: 5vb auto;
45 | }
46 |
47 | .smol-breakout-grid > *:not(.breakout) {
48 | grid-column: content;
49 | }
50 |
51 | .smol-breakout-grid > .breakout {
52 | grid-column: grid;
53 | }
54 | {% endset %}
55 |
56 | {% set html %}
57 |
Chocolate cake macaroon tootsie roll pastry gummies.
90 |
91 |
92 |
Card Headline 3
93 |
Apple pie jujubes cheesecake ice cream gummies sweet roll lollipop.
94 |
Chocolate cake macaroon tootsie roll pastry gummies.
95 |
96 |
97 |
98 | {% endset %}
99 |
100 | {% set resourceLink %}
101 |
102 | Learn more about CSS selectors in my two part guide, where part one covers [selectors and combinators](https://moderncss.dev/guide-to-advanced-css-selectors-part-one/) and part two covers [pseudo classes and pseudo elements](https://moderncss.dev/guide-to-advanced-css-selectors-part-two/).
103 |
104 | {% endset %}
105 |
106 | {% set codepenForm %}
107 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html, 'grid' %}
108 | {% endset %}
109 |
110 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-container.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Intrinsic Container"
4 | categories: ["utility", "layout"]
5 | date: 2022-05-05
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | This modern CSS container recipe has three delicious ingredients: the `min()` function, the logical property `margin-inline`, and a custom property - `--container-max` - to make it flexible across infinite contexts.
12 |
13 | Logical properties are writing mode-aware properties that can also serve as shorthand in some cases. Here, `margin-inline` is a shorthand for setting both `margin-left` and `margin-right`, and [`margin-inline` has fairly good support](https://caniuse.com/mdn-css_properties_margin-inline). A PostCSS plugin is available if you're unable to upgrade quite yet for your audience.
14 |
15 | Review the "[Smol Flexible Unbreakable Boxes](#smol-unbreakable-boxes)" for an explanation of `min()`,
16 |
17 | {% endset %}
18 |
19 | {% set css %}
20 | .smol-container {
21 | width: min(100% - 3rem, var(--container-max, 60ch));
22 | margin-inline: auto;
23 | }
24 | {% endset %}
25 |
26 | {% set html %}
27 |
28 |
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ex quos doloribus autem praesentium quod voluptatem quaerat accusamus labore ullam, omnis corrupti aperiam natus fuga repudiandae repellendus, ipsa enim vitae ut!
29 |
30 | {% endset %}
31 |
32 | {% set resourceLink %}
33 |
34 | Get a bit more info about this container class and [review additional uses of `min()`](https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#the-modern-css-container-class) and other CSS math functions on ModernCSS.dev.
35 |
36 | {% endset %}
37 |
38 | {% set codepenForm %}
39 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
40 | {% endset %}
41 |
42 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-css-grid.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Responsive CSS Grid"
4 | categories: ["grid", "layout"]
5 | date: 2021-02-10
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | Create an intrinsically responsive grid layout, optionally using a CSS custom property to extend to variable contexts. Each column will resize at the same rate, and items will begin to break to a new row if the width reaches the `--min` value.
12 |
13 | {% endset %}
14 |
15 | {% set css %}
16 | .smol-css-grid {
17 | --min: 15ch;
18 | --gap: 1rem;
19 |
20 | display: grid;
21 | grid-gap: var(--gap);
22 | /* min() with 100% prevents overflow
23 | in extra narrow spaces */
24 | grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--min)), 1fr));
25 | }
26 | {% endset %}
27 |
28 | {% set html %}
29 |
30 |
Item 1
31 |
Item 2
32 |
Item 3
33 |
34 | {% endset %}
35 |
36 | {% set resourceLink %}
37 |
38 | Learn more about [creating a responsive CSS grid solution](https://moderncss.dev/solutions-to-replace-the-12-column-grid/) on ModernCSS.dev
39 |
40 | {% endset %}
41 |
42 | {% set codepenForm %}
43 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
44 | {% endset %}
45 |
46 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-document-styles.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Document Styles"
4 | categories: ["layout"]
5 | date: 2021-02-28
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set hideDemo %}true{% endset %}
10 |
11 | {% set description %}
12 |
13 | In 55 smol lines of CSS, we've created a set of reasonable document styles that is _just enough_ to produce a responsive, easily readable document given the use of semantic HTML. Thanks to `flexbox`, `viewport units`, and `clamp`, it's flexible for variable document lengths. The newly supported `:is()` deserves the most credit in terms of reducing lines of code.
14 |
15 | While lines of code (short or long) certainly doesn't automatically mean "quality", this demo shows that for a simple project you may not need a framework when a little dash of carefully applied CSS will do!
16 |
17 | In fact - it's a great starting point to expand from to use the other smol techniques 😉
18 |
19 | > This particular snippet is for an _entire webpage_ so consequently the demo is not available for direct preview. **Select "Open in Codepen"** to check out the following styles in context!
20 |
21 | {% endset %}
22 |
23 | {% set css %}
24 | * {
25 | box-sizing: border-box;
26 | margin: 0;
27 | }
28 |
29 | body {
30 | display: flex;
31 | flex-direction: column;
32 | min-height: 100vh;
33 | padding: 5vh clamp(1rem, 5vw, 3rem) 1rem;
34 | font-family: system-ui, sans-serif;
35 | line-height: 1.5;
36 | color: #222;
37 | }
38 |
39 | body > * {
40 | --layout-spacing: max(8vh, 3rem);
41 | --max-width: 70ch;
42 | width: min(100%, var(--max-width));
43 | margin-left: auto;
44 | margin-right: auto;
45 | }
46 |
47 | main {
48 | margin-top: var(--layout-spacing);
49 | }
50 |
51 | footer {
52 | margin-top: auto;
53 | padding-top: var(--layout-spacing);
54 | }
55 |
56 | footer p {
57 | border-top: 1px solid #ccc;
58 | padding-top: 0.25em;
59 | font-size: 0.9rem;
60 | color: #767676;
61 | }
62 |
63 | :is(h1, h2, h3) {
64 | line-height: 1.2;
65 | }
66 |
67 | :is(h2, h3):not(:first-child) {
68 | margin-top: 2em;
69 | }
70 |
71 | article * + * {
72 | margin-top: 1em;
73 | }
74 |
75 | a {
76 | color: navy;
77 | text-underline-offset: 0.15em;
78 | }
79 | {% endset %}
80 |
81 | {% set html %}
82 |
83 |
Minimal Modern CSS Document Styles
84 |
85 |
86 |
87 |
55 lines of CSS
88 |
This set of reasonable document styles is just enough to produce a responsive, easily readable document given the use of semantic HTML. Thanks to flexbox, viewport units, and clamp, it's flexible for variable document lengths. The newlys supported :is() deserves the most credit in terms of reducing lines of code.
89 |
While lines of code (short or long) certainly doesn't automatically mean "quality", this demo shows that for a simple project you may not need a framework when a little dash of carefully applied CSS will do!
90 |
No classes, all selectors and combinators
91 |
Little rusty on your selectors and combinators? Check out my two-part guide to advanced CSS selectors, starting with part one.
92 |
Sugar plum pie sweet roll chocolate bar chocolate cake jujubes jelly beans lollipop. Caramels muffin toffee bonbon icing wafer toffee tiramisu lemon drops.
94 |
95 |
96 |
99 | {% endset %}
100 |
101 | {% set resourceLink %}
102 |
103 | **Little rusty on your selectors and combinators**? Check out my two-part guide to advanced CSS selectors, [starting with part one](https://moderncss.dev/guide-to-advanced-css-selectors-part-one/). These styles are used and very slightly expanded (~120 lines) in my [Smol 11ty Starter](https://smol-11ty-starter.netlify.app/).
104 |
105 | {% endset %}
106 |
107 | {% set codepenForm %}
108 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html, 'unstyled' %}
109 | {% endset %}
110 |
111 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-flexbox-grid.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Responsive Flexbox Grid"
4 | categories: ["flexbox", "layout"]
5 | date: 2021-02-10
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | Create an intrinsically responsive grid layout, optionally using a CSS custom property to extend to variable contexts. Each column will resize at the same rate _until_ reaching the `--min` width. At that point, the last item will break to a new row and fill any available space.
12 |
13 | {% endset %}
14 |
15 | {% set css %}
16 | .smol-flexbox-grid {
17 | --min: 10ch;
18 | --gap: 1rem;
19 |
20 | display: flex;
21 | flex-wrap: wrap;
22 | gap: var(--gap);
23 | }
24 |
25 | .smol-flexbox-grid > * {
26 | flex: 1 1 var(--min);
27 | }
28 | {% endset %}
29 |
30 | {% set html %}
31 |
32 |
Item 1
33 |
Item 2
34 |
Item 3
35 |
36 | {% endset %}
37 |
38 | {% set resourceLink %}
39 |
40 | Learn more about [creating a responsive flexbox grid solution](https://moderncss.dev/solutions-to-replace-the-12-column-grid/) on ModernCSS.dev
41 |
42 | {% endset %}
43 |
44 | {% set codepenForm %}
45 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
46 | {% endset %}
47 |
48 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-focus-styles.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Focus Styles"
4 | categories: ["utility"]
5 | date: 2021-06-10
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set resize %}false{% endset %}
10 |
11 | {% set description %}
12 |
13 | Focus styles are incredibly important for the accessibility of your application. But, it can be difficult to manage them across changing contexts.
14 |
15 | The following solution takes advantage of custom properties and `:is()` to set reasonable defaults for interactive elements. Then, individual instances can override each setting by simply providing an alternate value for the custom property.
16 |
17 | This solution sets `currentColor` as the `outline-color` which works in many contexts. One that it might not is for buttons, in which case you could update `--outline-color` to use the same color as the button background, for example.
18 |
19 | Additionally, this demonstrates one of the newly available CSS pseudo-class selectors: `focus-visible`. This selector is intended to only display focus styles when the browser detects that they should be visible (you may encounter the term "heuristics"). For now, we've setup some fallbacks so that a focus style is presented even when a browser doesn't support `:focus-visible` quite yet.
20 |
21 | **Note**: Due to using `:focus-visible`, you may not see focus styles on the links and buttons unless you tab to them.
22 |
23 | > Make sure your focus styles have appropriate contrast! If you're working on buttons, [generate an accessible color palette with ButtonBuddy](https://buttonbuddy.dev)
24 |
25 | {% endset %}
26 |
27 | {% set css %}
28 |
29 | /* For "real-world" usage, you do not need to scope
30 | these custom properties */
31 | .smol-focus-styles :is(a, button, input, textarea, summary) {
32 | /* Using max() ensures at least a value of 2px,
33 | while allowing the possibility of scaling
34 | relative to the component */
35 | --outline-size: max(2px, 0.08em);
36 | --outline-style: solid;
37 | --outline-color: currentColor;
38 | }
39 |
40 | /* Base :focus styles for fallback purposes */
41 | .smol-focus-styles :is(a, button, input, textarea, summary):focus {
42 | outline: var(--outline-size) var(--outline-style) var(--outline-color);
43 | outline-offset: var(--outline-offset, var(--outline-size));
44 | }
45 |
46 | /* Final :focus-visible styles */
47 | .smol-focus-styles :is(a, button, input, textarea):focus-visible {
48 | outline: var(--outline-size) var(--outline-style) var(--outline-color);
49 | outline-offset: var(--outline-offset, var(--outline-size));
50 | }
51 |
52 | /* Remove base :focus styles when :focus-visible is available */
53 | .smol-focus-styles :is(a, button, input, textarea):focus:not(:focus-visible) {
54 | outline: none;
55 | }
56 |
57 | /* Demonstration of customizing */
58 | .smol-focus-styles li:nth-of-type(2) a {
59 | --outline-style: dashed;
60 | }
61 |
62 | .smol-focus-styles input {
63 | --outline-color: red;
64 | }
65 |
66 | .smol-focus-styles textarea {
67 | --outline-size: 0.25em;
68 | --outline-style: dotted;
69 | --outline-color: green;
70 | }
71 |
72 | .smol-focus-styles li:nth-of-type(4) button {
73 | font-size: 2.5rem;
74 | }
75 | {% endset %}
76 |
77 | {% set html %}
78 |
70 | {% endset %}
71 |
72 | {% set resourceLink %}
73 |
74 | If you need more style control, you may still want to use an alternate method. Review my tutorial on creating [totally custom list styles](https://moderncss.dev/totally-custom-list-styles/).
75 |
76 | {% endset %}
77 |
78 | {% set codepenForm %}
79 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html, 'grid' %}
80 | {% endset %}
81 |
82 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-responsive-padding.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Responsive Padding"
4 | categories: ["layout"]
5 | date: 2021-02-11
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | This smol demo is using `clamp()` for responsive padding. The order of `clamp()` values can be interpreted as: the minimum allowed value is `1rem`, the ideal value is `5%` (which will be relative to the element), and the max allowed value is `3rem`.
12 |
13 | In other words, as the element is placed in different contexts and resized across viewports, that value will grow and shrink. But it will always compute to a value within the range of `1rem` to `3rem`.
14 |
15 | Another suggested option for the middle ideal value is to use a viewport unit, like `4vw`, which works great for components such as models or setting padding on the `body`.
16 |
17 | {% endset %}
18 |
19 | {% set css %}
20 | .smol-responsive-padding {
21 | padding: 1.5rem clamp(1rem, 5%, 3rem);
22 | }
23 | {% endset %}
24 |
25 | {% set html %}
26 |
29 | {% endset %}
30 |
31 | {% set resourceLink %}
32 |
33 | Explore [more techniques for contextual, intrinsic spacing](https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/) on ModernCSS.dev.
34 |
35 | {% endset %}
36 |
37 | {% set codepenForm %}
38 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
39 | {% endset %}
40 |
41 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-responsive-sidebar.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Responsive Sidebar Layout"
4 | categories: ["grid", "layout"]
5 | date: 2021-02-11
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | Several layers of delicious modern CSS goodness here! First, we're using `fit-content` to handle the sidebar sizing. This allows the sidebar to grow _up to_ the defined value, but only if it _needs to_, else it will use/shrink to the equivalent of `min-content`.
12 |
13 | Next, we use `minmax` for the main content. Why? Because if we only use `1fr` then eventually our sidebar and main will share 50% of the space, and we want the main area to always be wider. We also nest `min` to ask the browser to use the minimum of _either_ of the options. The result in this case is use of `50vw` on mobile-sized viewports, and `30ch` on larger viewports. And, when there's room, it also stretches to `1fr` for the _max_ part of `minmax` 🙌🏽
14 |
15 | {% endset %}
16 |
17 | {% set css %}
18 | .smol-sidebar {
19 | display: grid;
20 | grid-template-columns: fit-content(20ch) minmax(min(50vw, 30ch), 1fr);
21 | }
22 | {% endset %}
23 |
24 | {% set html %}
25 |
26 |
27 | Some sidebar content goes here
28 |
29 | Main content
30 |
31 | {% endset %}
32 |
33 | {% set codepenForm %}
34 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
35 | {% endset %}
36 |
37 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-scroll-snap.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Scroll Snap"
4 | categories: ["grid", "layout"]
5 | date: 2021-02-21
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set resize %}false{% endset %}
10 |
11 | {% set description %}
12 |
13 | Modern CSS has gifted us a series of properties that enable setting up more controlled scrolling experiences. In this demo, you'll find that as you begin to scroll, the middle items "snap" to the center of the scrollable area. Additionally, you are unable to scroll past more than one item at a time.
14 |
15 | To align the scroll items, we're using grid and updating the orientation of child items using `grid-auto-flow: column`. Then the width of the grid children is set using `min()` which selects the _minimum computed value_ between the options provided. The selected width options in this demo results in a large section of neighboring items being visible in the scrollable area for large viewports, while on smaller viewports the scrollable area is mostly consumed by the current scroll item.
16 |
17 | _While this is a very cool feature set, use with care!_ Be sure to test your implementation to ensure its not inaccessible. Test across a variety of devices, and with desktop zoom particularly at levels of 200% and 400% to check for overlap and how a changed aspect ratio affects scroll items. Try it out with a screen reader and make sure you can navigate to all content.
18 |
19 | **Note**: Have caution when attempting to mix fullscreen scroll snap slideshows followed by normal flow content. This can damage the overall scrolling experience and even prevent access to content. Fullscreen scroll areas are also prone to issues for users of high desktop zoom due to high risk of overlapping content as the aspect ratio changes. In addition, fullscreen versions that use `y mandatory` result in "scroll hijacking" which can be frustrating to users.
20 |
21 | Also - you may have a pleasant smooth scroll experience on a touchpad or magic mouse. But mouse users who rely on interacting with the scroll bar arrows or use a click wheel can have a jarring experience. This is due to browser and OS inconsistencies in handling the snapping based on input method (an issue was specifically reported for this demo using Chrome and Edge on PC).
22 |
23 | {% endset %}
24 |
25 | {% set css %}
26 | .smol-scroll-snap {
27 | /* Set up container positioning */
28 | display: grid;
29 | grid-auto-flow: column;
30 | grid-gap: 1.5rem;
31 | /* Enable overflow along our scroll axis */
32 | overflow-x: auto;
33 | /* Define axis and scroll type, where
34 | `mandatory` means any scroll attempt will
35 | cause a scroll to the next item */
36 | scroll-snap-type: x mandatory;
37 | padding: 0 0 1.5rem;
38 | -webkit-overflow-scrolling: touch;
39 | }
40 |
41 | .smol-scroll-snap > * {
42 | width: min(45ch, 60vw);
43 | /* Choose how to align children on scroll */
44 | scroll-snap-align: center;
45 | /* Prevents scrolling past more than one child */
46 | scroll-snap-stop: always;
47 | }
48 | {% endset %}
49 |
50 | {% set html %}
51 |
68 | {% endset %}
69 |
70 | {% set resourceLink %}{% endset %}
71 |
72 | {% set codepenForm %}
73 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
74 | {% endset %}
75 |
76 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-stack-layout.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Stack Layout"
4 | categories: ["grid", "layout"]
5 | date: 2021-02-19
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | This smol stacked layout is a grid feature that can often replace older techniques that relied on absolute positioning. It works by defining a single `grid-template-area` and then assigning all direct children to that `grid-area`. The children are then "stacked" and can take advantage of grid positioning, such as the centering technique in the demo.
12 |
13 | At first glance you might not notice, but that's a video background in the demo. And all we had to do was set `width: 100%` to ensure it filled the grid area. Then, we make use of `place-self` on the `h3` to center it. The rest is completely optional design styling!
14 |
15 | Bonus features in this demo include defining the `h3` size using `clamp()` for _viewport relative sizing_, and also using `aspect-ratio` to size the container to help reduce [cumulative layout shift](https://web.dev/cls/).
16 |
17 | {% endset %}
18 |
19 | {% set css %}
20 | .smol-stack-layout {
21 | display: grid;
22 | grid-template-areas: "stack";
23 | /* Set within the HTML for the demo */
24 | aspect-ratio: var(--stack-aspect-ratio);
25 | background-color: #200070;
26 | }
27 |
28 | .smol-stack-layout > * {
29 | grid-area: stack;
30 | }
31 |
32 | .smol-stack-layout video {
33 | width: 100%;
34 | }
35 |
36 | .smol-stack-layout h3 {
37 | place-self: center;
38 | font-size: clamp(2.5rem, 5vw, 5rem);
39 | text-align: center;
40 | line-height: 1;
41 | font-style: italic;
42 | padding: 5vh 2vw;
43 | }
44 |
45 | .smol-stack-layout--video small {
46 | align-self: end;
47 | justify-self: start;
48 | padding: 0 0 0.25em 0.5em;
49 | opacity: 0.8;
50 | font-size: 0.8rem;
51 | }
52 |
53 | .smol-stack-layout h3,
54 | .smol-stack-layout small {
55 | position: relative;
56 | color: #fff;
57 | }
58 | {% endset %}
59 |
60 | {% set html %}
61 |
68 | {% endset %}
69 |
70 | {% set resourceLink %}
71 |
72 | For more examples of creating smol stack layouts, review my guide to [website hero designs](https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/), see how it's used for [creating custom checkboxes](https://moderncss.dev/pure-css-custom-checkbox-style/), and learn to use it to [position animated image captions](https://moderncss.dev/animated-image-gallery-captions-with-bonus-ken-burns-effect/).
73 |
74 | {% endset %}
75 |
76 | {% set codepenForm %}
77 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
78 | {% endset %}
79 |
80 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-transitions.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Transitions"
4 | categories: ["utility"]
5 | date: 2021-02-21
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set resize %}false{% endset %}
10 |
11 | {% set description %}
12 |
13 | This set of performant CSS transition utility classes include CSS custom properties for scaling the transition property and duration. We're doing a few things in this demo that you may want to keep in mind if you use them.
14 |
15 | First, we're triggering the transition of the _child elements_ on `:hover` of the parent. The reason for this is that for transitions that move the element, it could end up moving out from under the mouse and causing a flicker between states. The `rise` transition is particularly in danger of that behavior.
16 |
17 | Second, we wrap our effect class in a media query check for `prefers-reduced-motion: reduce` that instantly jumps the transition to the final state. This is to comply with the request for reduced motion by effectively disabling the animated part of the transition.
18 |
19 | {% endset %}
20 |
21 | {% set css %}
22 | .smol-transitions > * {
23 | --transition-property: transform;
24 | --transition-duration: 180ms;
25 |
26 | transition: var(--transition-property) var(--transition-duration) ease-in-out;
27 | }
28 |
29 | .rise:hover > * {
30 | transform: translateY(-25%);
31 | }
32 |
33 | .rotate:hover > * {
34 | transform: rotate(15deg);
35 | }
36 |
37 | .zoom:hover > * {
38 | transform: scale(1.1);
39 | }
40 |
41 | .fade > * {
42 | --transition-property: opacity;
43 | --transition-duration: 500ms;
44 | }
45 |
46 | .fade:hover > * {
47 | opacity: 0;
48 | }
49 |
50 | @media (prefers-reduced-motion: reduce) {
51 | .smol-transitions > * {
52 | --transition-duration: 0.01ms;
53 | }
54 | }
55 | {% endset %}
56 |
57 | {% set html %}
58 |
59 |
rise
60 |
rotate
61 |
zoom
62 |
fade
63 |
64 | {% endset %}
65 |
66 | {% set resourceLink %}{% endset %}
67 |
68 | {% set codepenForm %}
69 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html, 'flex' %}
70 | {% endset %}
71 |
72 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-unbreakable-boxes.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Flexible Unbreakable Boxes"
4 | categories: ["component", "layout"]
5 | date: 2021-04-09
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set description %}
10 |
11 | [CSS is awesome](https://css-tricks.com/css-is-in-fact-awesome/), and using a mix of older and modern CSS we can quickly define flexible, unbreakable boxes!
12 |
13 | This demo of a `blockquote` first reuses our [responsive padding](#smol-responsive-padding) idea to enable padding that _just feels right_ as the box flexes to different sizes. The box size is controlled by setting width using the `min()` function. As the box grows and shrinks, `min()` will select the _minimum_ of the provided values, resulting in a box that has a `max-width` for large viewports _and_ a `max-width` on smaller viewports.
14 |
15 | Then we add a few properties to ensure long text values cannot break the box, including `word-break` and `hyphens` (note that [`hyphens`](https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#browser_compatibility) may not work in all languages).
16 |
17 | Finally, the `footer` sets its width with `fit-content` just as we also used in the previous [visited styles demo](#smol-visited-styles). This makes for a great alternative to swapping to an `inline` display value in case you also need to set a `display`!
18 |
19 | {% endset %}
20 |
21 | {% set css %}
22 | .smol-unbreakable-box {
23 | --color-light: #E0D4F6;
24 | --color-dark: #675883;
25 |
26 | margin: 2rem auto;
27 | color: var(--color-dark);
28 | background-color: var(--color-light);
29 | font-size: 1.15rem;
30 | /* Smol Responsive Padding FTW! */
31 | padding: clamp(.75rem, 3%, 2rem);
32 | /* Provide a max-width and prevent overflow */
33 | width: min(50ch, 90%);
34 | /* Help prevent overflow of long words/names/URLs */
35 | word-break: break-word;
36 | /* Optional, not supported for all languages */
37 | hyphens: auto;
38 | }
39 |
40 | .smol-unbreakable-box footer {
41 | padding: 0.25em 0.5em;
42 | margin-top: 1rem;
43 | color: var(--color-light);
44 | background-color: var(--color-dark);
45 | font-size: 0.9rem;
46 | /* Creates a visual box shrunk to `max-content` */
47 | width: fit-content;
48 | }
49 | {% endset %}
50 |
51 | {% set html %}
52 |
58 | {% endset %}
59 |
60 | {% set resourceLink %}
61 |
62 | For more extensive info and another example for creating flexible, unbreakable boxes, check out this ModernCSS tutorial - [Developing For Imperfect: Future Proofing CSS Styles](https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/)
63 |
64 | {% endset %}
65 |
66 | {% set codepenForm %}
67 | {% postToCodepen meta.title, page.fileSlug, meta.categories, css, html %}
68 | {% endset %}
69 |
70 | {% include "code.njk" %}
--------------------------------------------------------------------------------
/src/snippets/smol-visited-styles.njk:
--------------------------------------------------------------------------------
1 | ---
2 | meta:
3 | title: "Smol Visited Styles"
4 | categories: ["utility"]
5 | date: 2021-03-02
6 | templateEngineOverride: njk, md
7 | ---
8 |
9 | {% set resize %}false{% endset %}
10 |
11 | {% set description %}
12 |
13 | The `:visited` pseudo class is very unique because of [the potential to be exploited in terms of user's privacy](https://developer.mozilla.org/en-US/docs/Web/CSS/Privacy_and_the_:visited_selector). To resolve this, browser makers have limited which CSS styles are allowed to be applied using `:visited`.
14 |
15 | A key gotcha is that styles applied via `:visited` will always use the parent's alpha channel - meaning, you cannot use `rgba` to go from invisible to visible, you must change the whole color value. So, to hide the initial state, you need to be able to use a solid color, such as the page or element's background color.
16 |
17 | As usual - this demo has bonus techniques and properties! Note the way we're updating the _order_ of the visited indicator using flexbox. And, we're using some newly supported properties to change the color, position, and style of the link underline.
18 |
19 | Plus, we're using `fit-content` again but as a value of `width` this time and not as a function. This means it will expand to the equivalent of `max-width` but not _exceed_ the available width, preventing overflow.
20 |
21 | {% endset %}
22 |
23 | {% set css %}
24 | ul.smol-visited-styles {
25 | --color-background: #fff;
26 | --color-accent: green;
27 |
28 | display: grid;
29 | grid-gap: 0.5rem;
30 | width: fit-content;
31 | padding: 1rem;
32 | border-radius: 0.5rem;
33 | background-color: var(--color-background);
34 | border: 1px solid var(--color-accent);
35 | }
36 |
37 | .smol-visited-styles a {
38 | display: flex;
39 | flex-direction: row-reverse;
40 | justify-content: flex-end;
41 | color: #222;
42 | text-decoration-color: var(--color-accent);
43 | text-decoration-style: dotted;
44 | text-underline-offset: 0.15em;
45 | }
46 |
47 | .smol-visited-styles a span {
48 | margin-right: 0.25em;
49 | /* Remove from normal document flow
50 | which excludes it from receiving
51 | the underline ✨ */
52 | float: left;
53 | }
54 |
55 | .smol-visited-styles a span::after {
56 | content: "✔";
57 | color: var(--color-background);
58 | }
59 |
60 | .smol-visited-styles a:visited span::after {
61 | color: var(--color-accent);
62 | }
63 | {% endset %}
64 |
65 | {% set html %}
66 |