Front End Web Development

A table with both a sticky header and a sticky first column

Css Tricks - Tue, 02/09/2021 - 3:06pm

We’ve covered that individual <table> cells, <th> and <td> can be position: sticky. It’s pretty easy to make the header of a table stick to the top of the screen while scrolling through a bunch or rows (like this demo).

But stickiness isn’t just for the top of the screen, you can stick things in any scroll direction (horizontal is just as fun). In fact, we can have multiple sticky elements stuck in different directions inside the same element, and even single elements that are stuck in multiple directions.

Here’s a video example of a table that sticks both the header and first column:

Why would you do that? Specifically for tabular data where cross-referencing is the point. In this table (which represents, of course, the scoring baseball game where somehow 20 teams are all playing each other at once because that’s how baseball works), it “makes sense” that you wouldn’t want the team name or the inning number to scroll away, as you’d lose context of what you’re looking at.

CodePen Embed Fallback

Not all tables need to be bi-directionally cross-referenceable. A lot of tables can smash rows into blocks on small screens for a better small-screen experience.

The “trick” at play here is partially the position: sticky; usage, but moreso to me, how you have to handle overlapping elements. A table cell that is sticky needs to have a background, because otherwise we’ll see overlapping content. It also needs proper z-index handling so that when it sticks in place, it’ll be on top of what it is supposed to be on top of. This feels like the trickiest part:

  • Make sure the tbody>th cells are above regular table cells, so they stay on top during a horizontal scroll.
  • Make sure the thead>th cells are above those, for vertical scrolling.
  • Make sure the thead>th:first-child cell is the very highest, as it needs to be above the body cells and it’s sibling headers again for horizontal scrolling.

A bit of a dance, but it’s doable.

High five to Cameron Clark who emailed me demoed this and showed me how cool it is. And indeed, Cameron, it is cool. When I shared that around, Estelle Weyl showed me a demo she made several years ago. That feels about right, Estelle is always a couple of years ahead of me.

The post A table with both a sticky header and a sticky first column appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Nested Media Queries

Css Tricks - Tue, 02/09/2021 - 10:34am

Using media queries in CSS as part of responsive websites is bread and butter stuff to todays front-end developer. Using preprocessors to make them more comfortable to write and easier to maintain has become common practice as well.

I spent a few months experimenting with a dozen different approaches to media queries in Sass and actually used a few in production. All of them eventually failed to cater for everything I needed to do in an elegant way. So I took what I liked about each of them and created a solution that covered all scenarios I came across.

Why use a preprocessor at all?

That’s a fair question. After all, what’s the point of doing all this if one can simply write media queries using pure CSS? Tidiness and maintainability.

The most common use for media queries is the transformation of a layout based on the browser’s viewport width. You can make a layout adapt in such a way that multiple devices with different screen sizes can enjoy an optimal experience. As a consequence, the expressions used to define the media queries will make reference to the typical screen width of those devices.

So if your code contains 5 media queries that target tablet devices with a width of 768px, you will hardcode that number 5 times, which is something ugly that my OCD would never forgive. First of all, I want my code to be easy to read to the point that anyone understands instantly that a media query is targeting tablet devices just by looking at it – I reckon the word tablet would do that better than 768px.

Also, what if that reference width changes in the future? I hate the idea of replacing it in 5 instances around the code, especially when it’s scattered around multiple files.

A first step would be to store that breakpoint in a variable and use it to construct the media query.

/* Using plain CSS */ @media (min-width: 768px) { } /* Using SCSS variables to store breakpoints */ $breakpoint-tablet: 768px; @media (min-width: $breakpoint-tablet) { }

Another reason to write media queries with a preprocessor like Sass is that it can sometimes provide some precious help with the syntax, in particular when writing an expression with a logical or (represented with a comma in CSS).

For example, if you want to target retina devices, the pure CSS syntax starts getting a bit verbose:

/* Plain CSS */ @media (min-width: 768px) and (-webkit-min-device-pixel-ratio: 2), (min-width: 768px) and (min-resolution: 192dpi) { } /* Using variables? */ @media (min-width: $bp-tablet) and ($retina) { // or #{$retina} }

It does look nicer, but unfortunately it won’t work as expected.

A problem with logic

Because of the way the CSS “or” operator works, I wouldn’t be able to mix the retina conditions with other expressions since a (b or c) would be compiled into (a or b) c and not a b or a c.

$retina: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)"; // This will generate unwanted results! @media (min-width: 480px) and #{$retina} { body { background-color: red; } } /* Not the logic we're looking for */ @media (min-width: 480px) and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { body { background-color: red; } }

I realized I needed something more powerful, like a mixin or a function, to address this. I tried a few solutions.

Dmitry Sheiko’s technique

One I tried was Dmitry Sheiko’s technique, which had a nice syntax and includes Chris’ retina declaration.

// Predefined Break-points $mediaMaxWidth: 1260px; $mediaBp1Width: 960px; $mediaMinWidth: 480px; @function translate-media-condition($c) { $condMap: ( "screen": "only screen", "print": "only print", "retina": "(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi)", ">maxWidth": "(min-width: #{$mediaMaxWidth + 1})", "<maxWidth": "(max-width: #{$mediaMaxWidth})", ">bp1Width": "(min-width: #{$mediaBp1Width + 1})", "<bp1Width": "(max-width: #{$mediaBp1Width})", ">minWidth": "(min-width: #{$mediaMinWidth + 1})", "<minWidth": "(max-width: #{$mediaMinWidth})" ); @return map-get( $condMap, $c ); } // The mdia mixin @mixin media($args...) { $query: ""; @each $arg in $args { $op: ""; @if ( $query != "" ) { $op: " and "; } $query: $query + $op + translate-media-condition($arg); } @media #{$query} { @content; } }

But the problem with logical disjunction was still there.

.section { @include media("retina", "<minWidth") { color: white; }; } /* Not the logic we're looking for */ @media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3 / 2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi) and (max-width: 480px) { .section { background: blue; color: white; } } Landon Schropp’s technique

Landon Schropp’s was my next stop. Landon creates simple named mixins that do specific jobs. Like:

$tablet-width: 768px; $desktop-width: 1024px; @mixin tablet { @media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) { @content; } } @mixin desktop { @media (min-width: #{$desktop-width}) { @content; } }

He has a single-responsibility retina version as well.

But another problem hit me when I was styling an element that required additional rules on intermediate breakpoints. I didn’t want to pollute my list of global breakpoints with case-specific values just so I could still use the mixin, but I definitely didn’t want to forgo the mixin and go back to using plain CSS and hardcoding things every time I had to use custom values.

/* I didn't want to sometimes have this */ @include tablet { } /* And other times this */ @media (min-width: 768px) and (max-width: 950px) { } Breakpoint technique

Breakpoint-sass was next on my list, as it supports both variables and custom values in its syntax (and, as a bonus, it’s really clever with pixel ratio media queries).

I could write something like:

$breakpoint-tablet: 768px; @include breakpoint(453px $breakpoint-tablet) { } @include breakpoint($breakpoint-tablet 850px) { } /* Compiles to: */ @media (min-width: 453px) and (max-width: 768px) { } @media (min-width: 768px) and (max-width: 850px) { }

Things were looking better, but I personally think that Breakpoint-sass’ syntax feels less natural than Dmitry’s. You can give it a number and it assumes it’s a min-width value, or a number and a string and it assumes a property/value pair, to name just a few of the combinations it supports.

That’s fine and I’m sure it works great once you’re used to it, but I hadn’t given up on finding a syntax that was both simple and as close as possible to the way I orally describe what a media query must target.

Also, if you look at the example above you’ll see that a device with a width of exactly 768px will trigger both media queries, which may not be exactly what we want. So I added the ability to write inclusive and exclusive breakpoints to my list of requirements.

My (Eduardo Bouças’s) technique

This is my take on it.

Clean syntax, dynamic declaration

I’m a fan of Dmitry’s syntax, so my solution was inspired by it. However, I’d like some more flexibility in the way breakpoints are created. Instead of hardcoding the names of the breakpoints in the mixin, I used a multidimensional map to declare and label them.

$breakpoints: (phone: 640px, tablet: 768px, desktop: 1024px) !default; @include media(">phone", "<tablet") { } @include media(">tablet", "<950px") { }

The mixin comes with a set of default breakpoints, which you can override anywhere in the code by re-declaring the variable $breakpoints.

Inclusive and exclusive breakpoints

I wanted to have a finer control over the intervals in the expressions, so I included support for the less-than-or-equal-to and greater-than-or-equal-to operators. This way I can use the same breakpoint declaration in two mutually exclusive media queries.

@include media(">=phone", "<tablet") { } @include media(">=tablet", "<=950px") { } /* Compiles to */ @media (min-width: 640px) and (max-width: 767px) { } @media (min-width: 768px) and (max-width: 950px) { } Infer media types and handle logic disjunction

Similarly to the breakpoints, there’s a list for media types and other static expressions declared by default (which you can override by setting the variable $media-expressions). This adds support for optional media types, such as screen or handheld, but it’s also capable of correctly handling expressions with logical disjunctions, such as the retina media query we saw before. The disjunctions are declared as nested lists of strings.

$media-expressions: (screen: "screen", handheld: "handheld", retina2x: ("(-webkit-min-device-pixel-ratio: 2)", "(min-resolution: 192dpi)")) !default; @include media("screen", ">=tablet") { } @include media(">tablet", "<=desktop", "retina2x") { } /* Compiles to */ @media screen and (min-width: 768px) { } @media (min-width: 769px) and (max-width: 1024px) and (-webkit-min-device-pixel-ratio: 2), (min-width: 769px) and (max-width: 1024px) and (min-resolution: 192dpi) { }

There’s no rocket science under the hood, but the full implementation of the mixin isn’t something I could show in just a few lines of code. Instead of boring you with huge code snippets and neverending comments, I included a Pen with everything working and I’ll briefly describe the process it goes through to construct the media queries.

CodePen Embed Fallback How it works
  1. The mixin receives multiple arguments as strings and starts by going through each one to figure out if it represents a breakpoint, a custom width, or one of the static media expressions.
  2. If an operator is found, it is extracted and any matching breakpoint will be returned, or else we assume it’s a custom value and cast it to a number (using SassyCast).
  3. If it’s a static media expression, it checks for any or operators and generates all the combinations necessary to represent the disjunction.
  4. The process is repeated for all the arguments and the results will by glued together by the and connector to form the media query expression.

If you’d like to look at the complete Sass for it, it’s here. It’s called include-media on GitHub.

Final thoughts
  • I’m a big fan of this technique to make Sass talk to JavaScript. Because we declare breakpoints as a multidimensional list with their names as keys, exporting them in bulk to JavaScript becomes really straightforward and can be done automatically with just a few lines of code.
  • I’m not trying to put down other people’s solutions and I’m definitely not saying this one is better. I mentioned them to show some of the obstacles I found along the way to my ideal solution, as well as some great things they introduced that inspired my own solution.
  • You might have some concerns about the length and complexity of this implementation. While I understand, the idea behind it is that you download one single file, @import it into your project and start using it without having to touch the source code. Ping me on Twitter though if you have any questions.
  • You can get it from GitHub and you are very welcome to contribute with issues/code/love. I’m sure there’s still a lot we can do to make it better.
Update!

Eduardo made a website for his approach: @include-media.

Direct Link to ArticlePermalink

The post Nested Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Recreating Game Elements for the Web: The Among Us Card Swipe

Css Tricks - Tue, 02/09/2021 - 6:13am

As a web developer, I pay close attention to the design of video games. From the HUD in Overwatch to the catch screen in Pokemon Go to hunting in Oregon Trail, games often have interesting mechanics and satisfying interactions, many of which inspire my own coding games at Codepip.

Beyond that, implementing small slices of these game designs on a web stack is a fun, effective way to broaden your skills. By focusing on a specific element, your time is spent working on an interesting part without having to build out a whole game with everything that entails. And even in this limited scope, you often get exposed to new technologies and techniques that push on the boundaries of your dev knowledge.

As a case study for this idea, I’ll walk you through my recreation of the card swipe from Among Us. For anyone in the dark, Among Us is a popular multiplayer game. Aboard a spaceship, crewmates have to deduce who among them is an imposter. All the while, they complete mundane maintenance tasks and avoid being offed by the imposter.

The card swipe is the most infamous of the maintenance tasks. Despite being simple, so many players have struggled with it that it’s become the stuff of streams and memes.

Here’s my demo

This is my rendition of the card swipe task:

CodePen Embed Fallback

Next, I’ll walk you through some of the techniques I used to create this demo.

Swiping with mouse and touch events

After quickly wireframing the major components in code, I had to make the card draggable. In the game, when you start dragging the card, it follows your pointer’s position horizontally, but stays aligned with the card reader vertically. The card has a limit in how far past the reader it can be dragged to its left or right. Lastly, when you lift your mouse or finger, the card returns to its original position.

All of this is accomplished by assigning functions to mouse and touch events. Three functions are all that‘s needed to handle mouse down, mouse move, and mouse up (or touch start, touch move, and touch end if you‘re on a touchscreen device). Here’s the skeleton of that JavaScript code:

const card = document.getElementById('card'); const reader = document.getElementById('reader'); let active = false; let initialX; // set event handlers document.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); document.addEventListener('touchstart', dragStart); document.addEventListener('touchmove', drag); document.addEventListener('touchend', dragEnd); function dragStart(e) { // continue only if drag started on card if (e.target !== card) return; // get initial pointer position if (e.type === 'touchstart') { initialX = e.touches[0].clientX; } else { initialX = e.clientX; } active = true; } function drag(e) { // continue only if drag started on card if (!active) return; e.preventDefault(); let x; // get current pointer position if (e.type === 'touchmove') { x = e.touches[0].clientX - initialX; } else { x = e.clientX - initialX; } // update card position setTranslate(x); } function dragEnd(e) { // continue only if drag started on card if (!active) return; e.preventDefault(); let x; // get final pointer position if (e.type === 'touchend') { x = e.touches[0].clientX - initialX; } else { x = e.clientX - initialX; } active = false; // reset card position setTranslate(0); } function setTranslate(x) { // don't let card move too far left or right if (x < 0) { x = 0; } else if (x > reader.offsetWidth) { x = reader.offsetWidth; } // set card position on center instead of left edge x -= (card.offsetWidth / 2); card.style.transform = 'translateX(' + x + 'px)'; } Setting status with performance.now()

Next, I had to determine whether the card swipe was valid or invalid. For it to be valid, you must drag the card across the reader at just the right speed. Didn’t drag it far enough? Invalid. Too fast? Invalid. Too slow? Invalid.

To find if the card has been swiped far enough, I checked the card’s position relative to the right edge of the card reader in the function dragEnd:

let status; // check if card wasn't swiped all the way if (x < reader.offsetWidth) { status = 'invalid'; } setStatus(status);

To measure the duration of the card swipe, I set start and end timestamps in dragStart and dragEnd respectively, using performance.now().

function setStatus(status) { // status is only set for incomplete swipes so far if (typeof status === 'undefined') { // timestamps taken at drag start and end using performance.now() let duration = timeEnd - timeStart; if (duration > 700) { status = 'slow'; } else if (duration < 400) { status = 'fast'; } else { status = 'valid'; } } // set [data-status] attribute on reader reader.dataset.status = status; }

Based on each condition, a different value is set on the reader’s data-status attribute. CSS is used to display the relevant message and illuminate either a red or green light.

#message:after { content: "Please swipe card"; } [data-status="invalid"] #message:after { content: "Bad read. Try again."; } [data-status="slow"] #message:after { content: "Too slow. Try again."; } [data-status="fast"] #message:after { content: "Too fast. Try again."; } [data-status="valid"] #message:after { content: "Accepted. Thank you."; } .red { background-color: #f52818; filter: saturate(0.6) brightness(0.7); } .green { background-color: #3dd022; filter: saturate(0.6) brightness(0.7); } [data-status="invalid"] .red, [data-status="slow"] .red, [data-status="fast"] .red, [data-status="valid"] .green { filter: none; } Final touches with fonts, animations, and audio

With the core functionality complete, I added a few more touches to get the project looking even more like Among Us.

First, I used a free custom font called DSEG to imitate the segmented type from old LCDs. All it took was hosting the files and declaring the font face in CSS.

@font-face { font-family: 'DSEG14Classic'; src: url('../fonts/DSEG14-Classic/DSEG14Classic-Regular.woff2') format('woff2'), url('../fonts/DSEG14-Classic/DSEG14Classic-Regular.woff') format('woff'), url('../fonts/DSEG14-Classic/DSEG14Classic-Regular.ttf') format('truetype'); }

Next, I copied the jitter animation of the text in the original. Game developers often add subtle animations to breath life into an element, like making a background drift or a character, well, breathe. To achieve the jitter, I defined a CSS animation:

@keyframes jitter { from { transform: translateX(0); } to { transform: translateX(5px); } }

At this point, the text glides smoothly back and forth. Instead, what I want is for it to jump back and forth five pixels at a time. Enter the steps() function:

#message { animation: jitter 3s infinite steps(2); }

Finally, I added the same audio feedback as used in Among Us.

let soundAccepted = new Audio('./audio/CardAccepted.mp3'); let soundDenied = new Audio('./audio/CardDenied.mp3'); if (status === 'valid') { soundAccepted.play(); } else { soundDenied.play(); }

Sound effects are often frowned upon in the web development world. A project like this an opportunity to run wild with audio.

And with that, the we’re done! Here’s that demo again:

CodePen Embed Fallback Try your own

Given how standardized the web has become in look and feel, this approach of pulling an element from a game and implementing it for the web is a good way to break out of your comfort zone and try something new.

Take this Among Us card swipe. In a small, simple demo, I tinkered with web fonts and animations in CSS. I monkeyed with input events and audio in JavaScript. I dabbled with an unconventional visual style.

Now it’s time for you to survey interesting mechanics from your favorite games and try your hand at replicating them. You might be surprised what you learn.

The post Recreating Game Elements for the Web: The Among Us Card Swipe appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

SVG within CSS

Css Tricks - Mon, 02/08/2021 - 11:07am

Stefan Judis has a “Today I Learned” (TIL) post explaining how SVGs filters can be inlined in CSS. The idea is that CSS has the filter property which supports some built-in functions, like grayscale(100%) and stuff like that.

But it can also point to a filter defined by SVG. So you could do filter: url(#my-custom-filter) which is in some inline <svg> as <filter id="my-custom-filter">. It’s kinda funny to have to refer out to the HTML like that. A filter is such a visual thing that it makes sense to bring it into the CSS. That looks like this:

img { filter: url('data:image/svg+xml,\ <svg xmlns="http://www.w3.org/2000/svg">\ <filter id="waves" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB">\ <feTurbulence type="turbulence" baseFrequency="0.01 0.01" numOctaves="1" seed="1" stitchTiles="noStitch" result="turbulence" />\ <feDisplacementMap in="SourceGraphic" in2="turbulence" scale="20" xChannelSelector="G" yChannelSelector="A" result="displacementMap" />\ </filter>\ </svg>#waves') ; }

That’s Stefan’s turbulence filter example, something CSS alone definitely cannot do.

Look at all those backslashes (\). Makes ya wish CSS had template literals, eh? Makes me nervous that a code formatter or minifier would choke on that, but I don’t actually know, maybe it would be fine.

What’s nice is that the SVG remains fairly intact (readable and editable). So here you can edit the SVG filter in the CSS and have a play:

CodePen Embed Fallback

I also think of Yoksel’s tools. This editor for two-tone and three-tone images is so cool. I can pick up one of those filters and drop it into some CSS as well:

CodePen Embed Fallback

Filters aren’t the only kind of SVG that makes some sense to inline into CSS though. You can put SVG drawing right into CSS as well.

CodePen Embed Fallback

This works everywhere but Safari in a quick blast through modern browsers. But I think in the not-so-distant past, we needed to encode more of the special characters in the SVG to get it to work (although you didn’t have to resort to base64). Yoskel’s URL-encoder is literally just for this — I just don’t know that it’s necessary anymore.

The post SVG within CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Animating a CSS Gradient Border

Css Tricks - Mon, 02/08/2021 - 5:24am

This little trick for gradient borders is super useful:

.border-gradient { border: 5px solid; border-image-slice: 1; border-image-source: linear-gradient(to left, #743ad5, #d53a9d); }

Here’s some basic demos from our article on the subject. Sephanie Eckles was sharing around the idea with more detail. Bramus Van Damme saw that and stretched it a bit by adding, then animating an angle to the gradient. Like:

div { --angle: 0deg; /* … */ border-image: linear-gradient(var(--angle), green, yellow) 1; animation: 10s rotate linear infinite; } @keyframes rotate { to { --angle: 360deg; } }

But wait! That’s not actually going to animate as-is. The browser doesn’t know that 360deg is an actual angle value, and not just some random string. If it did know it was an angle value, it could animate it. So, tell it:

@property --angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }

See Bramus’ article for the demos there. Bonafide CSS trick. I can’t wait for more support for @property (Chrome only, as I write), because it really unlocks some cool CSS trickery. Animating numbers visually, for example.

Direct Link to ArticlePermalink

The post Animating a CSS Gradient Border appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

(Jay Freestone’s) Front-end predictions for 2021

Css Tricks - Mon, 02/08/2021 - 5:24am

React framework maturity, early container queries, WASM adoption, and monoliths. I’ll take all four, please. Not feeling like a particularly front-end-y? Jay says:

Interestingly, the biggest developments in the front-end are unlikely to be traditionally front-end concerns. Back in our 2019 forecast, we noted that the role of the front-end developer was increasingly shifting towards ‘full-stack’, and this has borne out to be true. Even the evolution of our frameworks and tools suggests this, with an increased focus on data-fetching, concurrency, security and scalability.

If I’m allowed to predict, and thus manifest, improvements in the web platform in 2021, I’d like to predict HTML’s inert attribute shipping without a flag in all three browser engines.

Direct Link to ArticlePermalink

The post (Jay Freestone’s) Front-end predictions for 2021 appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Exploring the Complexities of Width and Height in CSS

Css Tricks - Fri, 02/05/2021 - 12:51pm

The following article is co-authored by Uri Shaked and Michal Porag.

Let’s explore the complexities of how CSS computes the width and height dimensions of elements. This is based on countless late-night hours debugging and fiddling with lots of combinations of CSS properties, reading though the specs, and trying to figure out why some things seem to behave one way or another.

But before jumping in, let’s cover some basics so we’re all on the same page.

The basics

You have an element, and you want it to be 640px wide and 360px tall. These are just arbitrary numbers that conform to 16:9 pixel ratio. You can set them explicitly like this:

.element { width: 640px; height: 360px; }

Now, the design calls for some padding inside that element. So you modify the CSS:

.element { width: 640px; height: 360px; padding: 10px; }

What is the rendered width and height of the element now? I bet you can guess… it’s not 640×360px anymore! It’s actually 660×380px, because the padding adds 10px to each side (i.e. top, right, bottom and left), for an additional 20px on both the height and width.

This has to do with the box-sizing property: if it’s set to content-box (the default value), the rendered size of the element is the width and height plus the padding and border. That might mean that the rendered size is bigger than we intend which is funny because it might wind up that an element’s declared width and height values are completely different than what’s rendered.

That’s the power of The CSS Box Model. It calculates width and height like so:

/* Width */ width + padding-left + padding-right + border-left + border-right /* Height */ height + padding-top + padding-bottom + border-top + border-bottom

What we just saw is how the dimensions for a block element are computed. Block elements include any element that naturally takes up the full width that’s available. So, by nature, it doesn’t matter how much content the element contains because its width is always 100%, that is, until we alter it. Think of elements like <p>, <article>, <main>, <div>, and so many more.

But now we ought to look at inline elements because they behave differently when it comes to The Box Model and how their dimensions are computed. After that, we’ll look at the relationship between parent and child elements, and how they affect the width and height computations of each other.

The curious case of inline elements

As we just saw, the padding and border of any element are both included in the element’s computed width and height dimensions. Funny enough, there are cases where the width and height properties have no effect whatsoever. Such is the case when working with inline elements.

An inline element is an element that’s width and height are determined by the content it contains. Inline elements, such as a <span>, will completely ignore the width and height as well the the top and bottom margin properties because, well, the content is what determines the dimensions. Here, sometimes a visual can help.

CodePen Embed Fallback

Just look at how nesting a block element inside of an inline element breaks the inline element’s shape simply because the block element is not defined by the amount of content it contains, but rather the amount of available space. You can really see that in action when we add a border to the inline element. Look how the inline element abruptly stops where the paragraph comes in, then continues after the paragraph.

CodePen Embed Fallback

The span sees the paragraph, which interrupts the inline flow of the span and essentially breaks out of it. Fascinating stuff!

But there’s more! Look how the inline element completely overlooks width and margin, even when those are declared right on it.

CodePen Embed Fallback

Crazy!

Parent and child elements

The parent-child relationship is a common pattern. A parent element is one that contains other elements nested inside it. And those nested elements are the parent element’s children.

<!-- The parent element --> <div class="parent"> <!-- The children --> <div class="child"></div> <div class="another-child"></div> <div class="twins">Whoa!</div> </div>

The width and height of an element gets super interesting with parent and child elements. Let’s look at all the interesting quirks we get with this.

Relative units

Let’s start with relative units. A relative unit is computed by its context, or relation to other elements. Yeah, that’s sort of a convoluted definition. There are several different types of relative units, but let’s take percentages as an example. We can say that an element is 100% wide.

<div class="child"> <!-- nothing yet --> </div> .parent { width: 100%; }

Cool. If we plop that element onto an empty page, it will take up 100% of the available horizontal space. And what that 100% computes to depends on the width of the browser, right? 100% of a browser that’s 1,500 pixels is 1,500 pixels wide. 100% of a browser that’s 500 pixels is 500 pixels wide. That’s what we mean by a relative unit. The actual computed value is determined by the context in which it’s used.

So, the astute reader may already be thinking: Hey, so that’s sort of like a child element that’s set to a parent element’s width. And that would be correct. The width of the child at 100% will compute based on the actual width of the parent element that contains it.

Height works much the same way: it’s relative to the parent’s height. For example, two parent elements with different height dimensions but identical children result in children with different heights.

CodePen Embed Fallback

Padding and margin

The width and height of parent-child combinations get even more interesting when we look at other properties, such as padding and margin. Apparently, when we specify a percentage value for padding or margin, it is always relative to the width of the parent, even when dealing with vertical edges.

Some clever designers have taken advantage of it to create boxes of equal width and height, or boxes that keep a certain aspect ratio when the page resizes. This is particularly useful for video or image content, but can also be (ab)used in creative ways. Go ahead, type whatever you want into the editable element in this demo. The box maintains a proportional height and width, no matter how much (or little) content is added.

CodePen Embed Fallback

This technique for creating aspect ratio boxes is lovingly referred to as the “padding hack.” Chris has covered it extensively. But now that we have the aspect-ratio property gaining wide browser support, there’s less reason to reach for it.

display: inline and inline-block

Now that we’ve taken looks at how parent and child element dimensions are computed, we should check out two other interesting property values that affect an element’s width: min-content and max-content.

These properties tell the browser to look at the content of the element in order to determine its width. For instance, if we have the text: “hello CSS encyclopedia, nice to meet you!”, the browser would calculate the space that text would take up on the screen, and use it as the width.

The difference between min-content and max-content lies in how the browser does this calculation. For max-content, the browser pretends it has infinite space, and lays all the text in a single line while measuring its width.

For min-content, the browser pretends it has zero space, so it puts every word / child inline element in a different line. Let’s see this in action:

CodePen Embed Fallback CodePen Embed Fallback

We actually saw max-content in action when we looked at the difference between block and inline elements. Inline elements, remember, are only as wide and tall as the content they contain. We can make most elements inline elements just by declaring display: inline; on it.

CodePen Embed Fallback

Cool. Another weapon we have is display: inline-block;. That creates an inline element, but enhanced with block-level computations in The Box Model. In other words, it’s an inline element that respects margin, width and height. The best of both worlds!

CodePen Embed Fallback Cyclic percentage size

Did that last point make sense? Well, hopefully I won’t confuse you with this:

CodePen Embed Fallback

The child element in this example has a relative width of 33%. The parent element does not have a width declared on it. How the heck is the child’s computed width get calculated when there’s nothing relative to it?

To answer that, we have to look at how the browser calculates the size of the elements in this example. We haven’t defined a specific width for the parent element, so the browser uses the initial value for width , which is auto. And since the parent element’s display is set to inline-block, auto behaves like max-content. And max-content, as we saw, should mean the parent element is as wide as the content in it, which is everything inside the child element.

So, the browser looks at the element’s content (children) to determine its width. However, the width of the child also depends on the parent’s width! Gah, this is weird!

The CSS Box Sizing Module specification calls this cyclic percentage sizing. I’m not sure why it’s called that exactly, but it details the complex math the browser has to do to (1) determine the parent element’s width, and (2) reconcile that width to the relative width of the child.

The process is actually pretty cool once you get over the math stuff. The browser starts by calculating a temporary width for the child before its declared value is applied. The temporary width the browser uses for the child is auto which we saw behaves like max-content which, in turn, tells the browser that the child needs to be as wide as the content it contains. And right now, that’s not the declared 33% value.

That max-content value is what the browser uses to calculate the parent’s width. The parent, you see, needs to be at least as wide as the content that it contains, which is everything in the child at max-content. Once that resolves, the browser goes back to the child element and applies the 33% value that’s declared in the CSS.

This is how it looks:

There! Now we know how a child element can contribute to the computed value of its parent.

M&Ms: the min- and max- properties

Hey, so you’re probably aware that the following properties exist:

  • min-width
  • min-height
  • max-width
  • max-height

Well, those have a lot to do with an element’s width and height as well. They specify the limits an element’s size. It’s like saying, Hey, browser, make sure this element is never under this width/height or above this width/height.

So, even if we have declared an explicit width on an element, say 100%, we can still cap that value by giving it a max-width:

element { width: 100%; max-width: 800px; }

This allows the browser to let the element take up as much space as it wants, up to 800 pixels. Let’s look what happens if we flip those values around and set the max-width to 100% and width to 800px:

element { width: 800px; max-width: 100%; }

Hey look, it seems to result in the exact same behavior! The element takes up all the space it needs until it gets to 800 pixels.

CodePen Embed Fallback

Apparently, things start to get more complex as soon as we add a parent element into the mix. This is the same example as above, with one notable change: now each element is a child of an inline-block element. Suddenly, we see a striking difference between the two examples:

CodePen Embed Fallback

Why the difference? To understand, let’s consider how the browser calculates the width of the elements in this example.

We start with the parent element (.parent). It has a display property set to inline-block, and we didn’t specify a width value for it. Just like before, the browser looks at the size of its children to determine its width. Each box in this example is wrapped in the .parent element.

The first box (#container1) has a percentage width value of 100%. The width of the parent resolves to the width of the text within (the child’s max-content), limited by the value we specified by max-width, and that is used to calculate the width of the child as well.

The second box (#container2) has a set width of 800px, so its parent width is also 800px — just wide enough to fit its child. Then, the child’s max-width is resolved relative to the parent’s final width, that is 800px. So both the parent and the child are 800px wide in this case.

So, even though we initially saw the two boxes behave the same when we swapped width and max-width values, we now know that isn’t always true. In this case, introducing a parent element set to display: inline-block; threw it all off!

Adding min(), max() and clamp() into the mix

The min(), max() and clamp() are three useful CSS functions that let us define the size of elements responsively… without media queries!

  • min(): Returns the minimum value of its arguments. The arguments can be given in different units, and we can even mix and match absolute and relative units, like min(800px, 100%).
  • max(): Returns the maximum value of its arguments. Just like min(), you can mix and match different units.
  • clamp(): A shorthand function for doing min and max at once: clamp(MIN, VAL, MAX) is resolved as max(MIN, min(VAL, MAX)). In other words, it will return VAL, unless it exceeds the boundaries defined by MIN and MAX, in which case it’ll return the corresponding boundary value.

Like this. Check out how we can effectively “re-write” the max-width example from above using a single CSS property:

.element { width: min(800px, 100%); } /* ...is equivalent to: */ .element { width: 800px; max-width: 100%; }

That would set the width of the element to 800px, but make sure we don’t exceed the width of the parent (100%). Just like before, if we wrap the element with an inline-block parent, we can observe it behaving differently than the max-width variation:

CodePen Embed Fallback

The width of the children (800px) is the same. However, if you enlarge the screen (or use CodePen’s 0.5x button to zoom out), you will notice that the second parent is actually larger.

It boils down to how the browser calculates the parent’s width: we didn’t specify a width for the parent, and as child’s width value is using relative units, the browser ignores it while calculating the parent’s width and uses the max-content child of the child, dictated by the “very long … long” text.

Wrapping up

Phew! It’s crazy that something as seemingly simple as width and height actually have a lot going on. Sure, we can set explicit width and height values on an element, but the actual values that render often end up being something completely different.

That’s the beauty (and, frankly, the frustration) with CSS. It’s so carefully considered and accounts for so many edge cases. I mean, the concept of The Box Model itself is wonderfully complex and elegant at the same time. Where else can we explicitly declare something in code and have it interpreted in different ways? The width isn’t always the width.

And we haven’t even touched on some other contributing factors to an element’s dimensions. Modern layout techniques, like CSS Flexbox and Grid introduce axes and track lines that also determine the rendered size of an element.

Authors: Uri Shaked and Michal Porag

The post Exploring the Complexities of Width and Height in CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Weekly Platform News: The :not() pseudo-class, Video Media Queries, clip-path: path() Support

Css Tricks - Fri, 02/05/2021 - 11:02am

Hey, we’re back with weekly updates about the browser landscape from Šime Vidas.

In this week’s update, the CSS :not pseudo class can accept complex selectors, how to disable smooth scrolling when using “Find on page…” in Chrome, Safari’s support for there media attribute on <video> elements, and the long-awaited debut of the path() function for the CSS clip-path property.

Let’s jump into the news…

The enhanced :not() pseudo-class enables new kinds of powerful selectors

After a years-long wait, the enhanced :not() pseudo-class has finally shipped in Chrome and Firefox, and is now supported in all major browser engines. This new version of :not() accepts complex selectors and even entire selector lists.

For example, you can now select all <p> elements that are not contained within an <article> element.

/* select all <p>s that are descendants of <article> */ article p { } /* NEW! */ /* select all <p>s that are not descendants of <article> */ p:not(article *) { }

In another example, you may want to select the first list item that does not have the hidden attribute (or any other attribute, for that matter). The best selector for this task would be :nth-child(1 of :not([hidden])), but the of notation is still only supported in Safari. Luckily, this unsupported selector can now be re-written using only the enhanced :not() pseudo-class.

/* select all non-hidden elements that are not preceded by a non-hidden sibling (i.e., select the first non-hidden child */ :not([hidden]):not(:not([hidden]) ~ :not([hidden])) { } CodePen Embed Fallback The HTTP Refresh header can be an accessibility issue

The HTTP Refresh header (and equivalent HTML <meta> tag) is a very old and widely supported non-standard feature that instructs the browser to automatically and periodically reload the page after a given amount of time.

<!-- refresh page after 60 seconds --> <meta http-equiv="refresh" content="60">

According to Google’s data, the <meta http-equiv="refresh"> tag is used by a whopping 2.8% of page loads in Chrome (down from 4% a year ago). All these websites risk failing several success criteria of the Web Content Accessibility Guidelines (WCAG):

If the time interval is too short, and there is no way to turn auto-refresh off, people who are blind will not have enough time to make their screen readers read the page before the page refreshes unexpectedly and causes the screen reader to begin reading at the top.

However, WCAG does allow using the <meta http-equiv="refresh"> tag specifically with the value 0 to perform a client-side redirect in the case that the author does not control the server and hence cannot perform a proper HTTP redirect.

(via Stefan Judis)

How to disable smooth scrolling for the “Find on page…” feature in Chrome

CSS scroll-behavior: smooth is supported in Chrome and Firefox. When this declaration is set on the <html> element, the browser scrolls the page “in a smooth fashion.” This applies to navigations, the standard scrolling APIs (e.g., window.scrollTo({ top: 0 })), and scroll snapping operations (CSS Scroll Snap).

Unfortunately, Chrome erroneously keeps smooth scrolling enabled even when the user performs a text search on the page (“Find on page…” feature). Some people find this annoying. Until that is fixed, you can use Christian Schaefer’s clever CSS workaround that effectively disables smooth scrolling for the “Find on page…” feature only.

@keyframes smoothscroll1 { from, to { scroll-behavior: smooth; } } @keyframes smoothscroll2 { from, to { scroll-behavior: smooth; } } html { animation: smoothscroll1 1s; } html:focus-within { animation-name: smoothscroll2; scroll-behavior: smooth; }

In the following demo, notice how clicking the links scrolls the page smoothly while searching for the words “top” and “bottom” scrolls the page instantly.

CodePen Embed Fallback Safari still supports the media attribute on video sources

With the HTML <video> element, it is possible to declare multiple video sources of different MIME types and encodings. This allows websites to use more modern and efficient video formats in supporting browsers, while providing a fallback for other browsers.

<video> <source src="/flower.webm" type="video/webm"> <source src="/flower.mp4" type="video/mp4"> </video>

In the past, browsers also supported the media attribute on video sources. For example, a web page could load a higher-resolution video if the user’s viewport exceeded a certain size.

<video> <source media="(min-width: 1200px)" src="/large.mp4" type="video/mp4"> <source src="/small.mp4" type="video/mp4"> </video>

The above syntax is in fact still supported in Safari today, but it was removed from other browsers around 2014 because it was not considered a good feature:

It is not appropriate for choosing between low resolution and high resolution because the environment can change (e.g., the user might fullscreen the video after it has begun loading and want high resolution). Also, bandwidth is not available in media queries, but even if it was, the user agent is in a better position to determine what is appropriate than the author.

Scott Jehl (Filament Group) argues that the removal of this feature was a mistake and that websites should be able to deliver responsive video sources using <video> alone.

For every video we embed in HTML, we’re stuck with the choice of serving source files that are potentially too large or small for many users’ devices … or resorting to more complicated server-side or scripted or third-party solutions to deliver a correct size.

Scott has written a proposal for the reintroduction of media in video <source> elements and is welcoming feedback.

The CSS clip-path: path() function ships in Chrome

It wasn’t mentioned in the latest “New in Chrome 88” article, but Chrome just shipped the path() function for the CSS clip-path property, which means that this feature is now supported in all three major browser engines (Safari, Firefox, and Chrome).

The path() function is defined in the CSS Shapes module, and it accepts an SVG path string. Chris calls this the ultimate syntax for the clip-path property because it can clip an element with “literally any shape.” For example, here’s a photo clipped with a heart shape:

CodePen Embed Fallback

The post Weekly Platform News: The :not() pseudo-class, Video Media Queries, clip-path: path() Support appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Some React Blog Posts I’ve Bookmarked and Read Lately

Css Tricks - Fri, 02/05/2021 - 5:38am
  • The React Hooks Announcement In Retrospect: 2 Years Later — Ryan Carniato considers hooks to be the most significant turning point in front end in the past five years, but he also says hooks have muddied the waters as well.
  • Mediator Component in React — Robin Wieruch’s article made me think just how un-opinionated React is and how architecturally on-your-own you are. It’s tough to find the right abstractions.
  • No One Ever Got Fired for Choosing React — Jake Lazaroff’s article is a good balance to the above. Sometimes you pick a library like React because it solves problems you’re likely to run into and lets you get to work.
  • A React “if” component — I kinda like how JSX does conditional rendering, but hey, a lightweight abstraction like this might just float your boat.
  • State of the React Ecosystem in 2021 — Hooks are big. State management is all over the place, and state machines are growing in popularity. webpack is still the main bundler. Everyone is holding their breath about Suspense… so suspenseful.
  • Blitz.js — “The Fullstack React Framework” — interesting to see a framework built on top of another framework (Next.js).
  • Introducing Zero-Bundle-Size React Server Components — Feels like it will be a big deal, but will shine brightest when frameworks and hosts zero-in on offerings around it. I’m sure Vercel and Netlify are all  &#x1f440;.

The post Some React Blog Posts I’ve Bookmarked and Read Lately appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Some Typography Blog Posts I’ve Bookmarked and Read Lately

Css Tricks - Thu, 02/04/2021 - 10:36am
  • Font-size: An Unexpectedly Complex CSS Property — From Manish Goregaokar in 2017. Of many oddities, I found the one where font: medium monospace renders at 13px where font: medium sans-serif renders at 16px particularly weird.
  • The good line-height — Since CSS supports unitless line-height, you probably shouldn’t be setting a hard number anyway.
  • Time to Say Goodbye to Google Fonts — Simon Wicki doesn’t mean don’t use them, they mean self-host them. Browsers are starting to isolate cache on a per-domain basis so that old argument that you buy speed because “users probably already have it cached” doesn’t hold up. I expected to hear about stuff like having more control over font loading, but this is just about the cache.
  • My Favorite Typefaces of 2020 — John Boardley’s picks for the past year. Have you seen these “color fonts”? They are so cool. Check out LiebeHeide, it looks like real pen-on-paper.
  • How to avoid layout shifts caused by web fonts — We’ve got CLS (Cumulative Layout Shift) now and it’s such an important performance metric that will soon start affecting SEO. And because we have CSS control over font loading via font-display, that means if we set things up such that font loading shifts the page, that’s bad. I like Simon Hearne’s suggestion that we tweak both our custom font and fallback font to match perfectly. I think perfect fallback fonts are one of the best CSS tricks.
  • How to pick a Typeface for User Interface and App Design? — Oliver Schöndorfer makes the case for “functional text” which is everything that isn’t body text (e.g. paragraphs of text) or display text (e.g. headers). “Clarity is key.”

The post Some Typography Blog Posts I’ve Bookmarked and Read Lately appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Lazy Load Routes in Vue with webpack Dynamic Comments

Css Tricks - Thu, 02/04/2021 - 5:53am

The way routing works in JavaScript is usually that you specify which relative URL pattern you want for which component to render. So for /about you want the <About /> component to render. Let’s take a look at how to do this in Vue/Vue Router with lazy loading, and do it as cleanly as possible. I use this little tip all the time in my own work.

A repo that includes everything covered in this post is available on GitHub.

You’ve probably seen Vue routes (URLs) like this:

import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import About from '../views/About.vue' import Login from '../views/Login.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About }, { path: '/login', name: 'Login', component: Login } ] const router = new VueRouter({ routes }) export default router

That will load the <Home /> component at the / route, the <About /> component at the /about route, and the <Login /> component at the /login route.

That doesn’t do a very good job of code splitting though, since all three of those components will be bundled together rather than loaded dynamically as needed.

Here’s another way to do the same, only with code splitting with dynamic import statements and webpack chunk names:

const routes = [ { path: '/', name: 'Home', component: () => import(/* webpackChunkName: "Home" */ '../views/Home.vue') }, { path: '/about', name: 'About', component: () => import(/* webpackChunkName: "About" */ '../views/About.vue') }, { path: '/login', name: 'Login', component: () => import(/* webpackChunkName: "Login" */ '../views/Login.vue') } ]

This is perfectly fine and doesn’t have any major downsides, other than being a bit verbose and repetitive. Since we’re awesome developers, let’s do a bit of abstraction to help, using an array that we’ll .map over.

const routeOptions = [ { path: '/', name: 'Home' }, { path: '/about', name: 'About' }, { path: '/login', name: 'Login' } ] const routes = routeOptions.map(route => { return { ...route, component: () => import(`@/views/${route.name}.vue`) } }) const router = new VueRouter({ routes })

Now we’ve reduced the use of the component key by using the route name as param in the import function.

But what happens if we want to set the chunk name?

As far as I know, you can’t have dynamic comments in JavaScript without some kind of build step. So, we are sacrificing comments (webpackChunkName) in favor of having to write less code in this case. It’s entirely up to you which you prefer.

Just kidding, let’s fix it.

As of webpack 2.6.0 , the placeholders [index] and [request] are supported, meaning we can set the name of the generated chunk like this:

// ... const routeOptions = [ { path: '/', name: 'Home' }, { path: '/about', name: 'About' }, { path: '/login', name: 'Login' } ] const routes = routeOptions.map(route => { return { ...route, component: () => import(/* webpackChunkName: "[request]" */ `../views/${route.name}.vue`) } }) const router = new VueRouter({ routes })

Nice! Now we have all the power, plus dynamically loaded routes with named chunks. And it works with Vue 2 and Vue 3. You can check it out by running npm run build in the terminal:

See that? Now the components are chunked out… and the build did all the naming for us!

Buuuuut, we can still take this one step further by grouping the lazy loaded routes into named chunks rather than individual components. For example, we can create groups that group our most important components together and the rest in another “not so important” group. We merely update the webpack chunk name in place of the [request] placeholder we used earlier:

const routes = [ { path: "/", name: "Home", component: () => import(/* webpackChunkName: "VeryImportantThings" */ "../views/Home.vue") }, { path: "/about", name: "About", component: () => import(/* webpackChunkName: "VeryImportantThings" */ "../views/About.vue") }, { path: "/login", name: "Login", component: () => import(/* webpackChunkName: "NotSoImportant" */ "../views/Login.vue") }, { path: "/contact", name: "Contact", component: () => import(/* webpackChunkName: "NotSoImportant" */ "../views/Contact.vue") } ];

Now our four components are groups into two separate chunks.

There you have it! A technique for lazy loading routes in Vue, plus some ideas for how to name and group them together at build.

The post Lazy Load Routes in Vue with webpack Dynamic Comments appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Algolia

Css Tricks - Thu, 02/04/2021 - 4:51am

Algolia is for search. Literally any website can take advantage of Algolia-powered search. You put JSON data (“records”) in, and then you can search them at lightning speed. The magic of Algolia is that they help you with both of those things: getting data in and getting search results out.

As far as getting data in, there are all sorts of ways. The most likely situation is that you already have data somewhere and you just need to give it to Algolia. They’ve got great docs on that. You basically write an integration that updates Algolia as you update your own data. There are so many things to help with this though. Got a WordPress site? They’ve got a PHP API client and people have built plugins around it.

\

Got a Rails site? They have integrations for you. What about a Jamstack site? No problem, they’ve got a Netlify build plugin. So, for example, your Jekyll site can have great search.

One way, and I’ve used this myself plenty of times, is literally entering records manually. While manually typing in records isn’t particularly scalable, I like that it’s possible.

So that’s all the “getting data in” part. Now the fun part: building a search UI experience. Great news here too: there is lots of help to make this awesome.

The core of it is InstantSearch.js, which Algolia provides directly. That native version also has versions in React, Vue, and Angular (you could make it work with anything). Wanna see it work super quickly? Try their Create InstantSearchApp flow, which will spin up a search UI super quickly for you.

While you don’t have to use any particular library, I find them very easy to use, very flexible configuration-wise, and no problem to style. Wanna see? CDNjs has everything on it in an Algolia index, so here’s a Pen that connects to that and provides a search UI:

CodePen Embed Fallback

You can see in the code how I’m controlling the number of results, the template and styles the results are shown in, and what happens when a search result is selected. That’s powerful stuff.

This is just the surface of Algolia though. Algolia is a very mature platform with all kinds of bells and whistles you can take advantage of. You can tweak algorithms, you can dig into analytics, you can take advantage of AI, you can use it on native mobile apps… and all with real customer support along the way.

The post Algolia appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Some Performance Blog Posts I’ve Bookmarked and Read Lately

Css Tricks - Wed, 02/03/2021 - 11:28am
  • Back/forward cache — I always assumed browsers just do fancy stuff with the back/forward buttons and us developers had very little control. Philip Walton tells us it’s critical that we understand “what makes pages eligible (and ineligible) for bfcache to maximize their cache-hit rates.” For example, if you use the unload event, the page is instantly disqualified from the cache.
  • Big picture performance analysis using Lighthouse Parade — Lighthouse only tests one page of your site. Lighthouse Parade tests all the URLs of a site, and aggregates the results.
  • Beyond fast with new performance features — Jake Archibald gets into the CSS content-visibility property (and a few other things) and how it can lead to incredible performance boosts (you use it to tell the browser that it’s straight-up OK not to render things). Right this minute, content-visiblity makes me nervous as it has issues with scrollbar jankiness and accessiblity problems. I found it a smidge confusing at first glance, and Tim Kadlec has reservations.
  • Image Decode & Visual Timings — Image performance isn’t only about the size of the image. Different formats take different amounts of time to decode and render. GIF never wins.
  • How to increase CSS-in-JS performance by 175x — The trick, readers, is shipping CSS. You can still use CSS-in-JS as you author, and have the build process create the CSS. They call that “Zero-Runtime” like Linaria.
  • Testing Performance — Kelly Sutton: “The best approach that I have found to preventing performance regressions is to employ qualitative assessments of the code.” Performance is such an unwieldy beast, that only in production will you truly know what happens.
  • Front-End Performance Checklist 2021 — If you’re going to get serious about performance this year, you’d do well to dig into Vitaly’s guide.
  • We rendered a million web pages to find out what makes the web slow — HTTP/2 is a huge indicator of good performance.

The post Some Performance Blog Posts I’ve Bookmarked and Read Lately appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Going From Solid to Knockout Text on Scroll

Css Tricks - Wed, 02/03/2021 - 6:31am

Here’s a fun CSS trick to show your friends: a large title that switches from a solid color to knockout text as the background image behind it scrolls into place. And we can do it using plain ol’ HTML and CSS!

This effect is created by rendering two containers with fixed <h1> elements. The first container has a white background with knockout text. The second container has a background image with white text. Then, using some fancy clipping tricks, we hide the first container’s text when the user scrolls beyond its boundaries and vice-versa. This creates the illusion that the text background is changing.

Before we begin, please note that this won’t work on older versions of Internet Explorer. Also, fixed background images can be cumbersome on mobile WebKit browsers. Be sure to think about fallback behavior for these circumstances.

Setting up the HTML

Let’s start by creating our general HTML structure. Inside an outer wrapper, we create two identical containers, each with an <h1> element that is wrapped in a .title_wrapper.

<header> <!-- First container --> <div class="container container_solid"> <div class="title_wrapper"> <h1>The Great Outdoors</h1> </div> </div> <!-- Second container --> <div class="container container_image"> <div class="title_wrapper"> <h1>The Great Outdoors</h1> </div> </div> </header>

Notice that each container has both a global .container class and its own identifier class — .container_solid and .container_image, respectively. That way, we can create common base styles and also target each container separately with CSS.

Initial styles

Now, let’s add some CSS to our containers. We want each container to be the full height of the screen. The first container needs a solid white background, which we can do on its .container_solid class. We also want to add a fixed background image to the second container, which we can do on its .container_image class.

.container { height: 100vh; } /* First container */ .container_solid { background: white; } /* Second container */ .container_image { /* Grab a free image from unsplash */ background-image: url(/path/to/img.jpg); background-size: 100vw auto; background-position: center; background-attachment: fixed; }

Next, we can style the <h1> elements a bit. The text inside .container_image can simply be white. However, to get knockout text for the <h1> element inside container_image, we need to apply a background image, then reach for the text-fill-color and background-clip CSS properties to apply the background to the text itself rather than the boundaries of the <h1> element. Notice that the <h1> background has the same sizing as that of our .container_image element. That’s important to make sure things line up.

.container_solid .title_wrapper h1 { /* The text background */ background: url(https://images.unsplash.com/photo-1575058752200-a9d6c0f41945?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ); background-size: 100vw auto; background-position: center; /* Clip the text, if possible */ /* Including -webkit` prefix for bester browser support */ /* https://caniuse.com/text-stroke */ -webkit-text-fill-color: transparent; text-fill-color: transparent; -webkit-background-clip: text; background-clip: text; /* Fallback text color */ color: black; } .container_image .title_wrapper h1 { color: white; }

Now, we want the text fixed to the center of the layout. We’ll add fixed positioning to our global .title_wrapper class and tack it to the vertical center of the window. Then we use text-align to horizontally center our <h1> elements.

.header-text { display: block; position: fixed; margin: auto; width: 100%; /* Center the text wrapper vertically */ top: 50%; -webkit-transform: translateY(-50%); -ms-transform: translateY(-50%); transform: translateY(-50%); } .header-text h1 { text-align: center; }

At this point, the <h1> in each container should be positioned directly on top of one another and stay fixed to the center of the window as the user scrolls. Here’s the full, organized, code with some shadow added to better see the text positioning.

CodePen Embed Fallback Clipping the text and containers

This is where things start to get really interesting. We only want a container’s <h1> to be visible when its current scroll position is within the boundaries of its parent container. Normally this can be solved using overflow: hidden; on the parent container. However, with both of our <h1> elements using fixed positioning, they are now positioned relative to the browser window, rather than the parent element. In this case using overflow: hidden; will have no effect.

For the parent containers to hide fixed overflow content, we can use the CSS clip property with absolute positioning. This tells our browser hide any content outside of an element’s boundaries. Let’s replace the styles for our .container class to make sure they don’t display any overflowing elements, even if those elements use fixed positioning.

.container { /* Hide fixed overflow contents */ clip: rect(0, auto, auto, 0); /* Does not work if overflow = visible */ overflow: hidden; /* Only works with absolute positioning */ position: absolute; /* Make sure containers are full-width and height */ height: 100vh; left: 0; width: 100%; }

Now that our containers use absolute positioning, they are removed from the normal flow of content. And, because of that, we need to manually position them relative to their respective parent element.

.container_solid { /* ... */ /* Position this container at the top of its parent element */ top: 0; } .container_image { /* ... */ /* Position the second container below the first container */ top: 100vh; }

At this point, the effect should be taking shape. You can see that scrolling creates an illusion where the knockout text appears to change backgrounds. Really, it is just our clipping mask revealing a different <h1> element depending on which parent container overlaps the center of the screen.

Let’s make Safari happy

If you are using Safari, you may have noticed that its render engine is not refreshing the view properly when scrolling. Add the following code to the .container class to force it to refresh correctly.

.container { /* ... */ /* Safari hack */ -webkit-mask-image: -webkit-linear-gradient(top, #ffffff 0%,#ffffff 100%); }

Here’s the complete code up to this point.

CodePen Embed Fallback Time to clean house

Let’s make sure our HTML is following accessibility best practices. Users not using assistive tech can’t tell that there are two identical <h1> elements in our document, but those using a screen reader sure will because both headings are announced. Let’s add aria-hidden to our second container to let screen readers know it is purely decorative.

<!-- Second container --> <div class="container container_image" aria-hidden="true"> <div class="title_wrapper"> <h1>The Great Outdoors</h1> </div> </div>

Now, the world is our oyster when it comes to styling. We are free to modify the fonts and font sizes to make the text just how we want. We could even take this further by adding a parallax effect or replacing the background image with a video. But, hey, at that point, just be sure to put a little additional work into the accessibility so those who prefer less motion get the right experience.

That wasn’t so hard, was it?

CodePen Embed Fallback

The post Going From Solid to Knockout Text on Scroll appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

The Web is for More Than Document Viewing

Css Tricks - Tue, 02/02/2021 - 3:17pm

I poked at a tweet from Ken Kocienda over the weekend:

“Water not wet” reports man standing in ocean. https://t.co/5oUUdWARTU

— Chris Coyier (@chriscoyier) January 31, 2021

I don’t know Ken, so I feel a little bad for being harsh. But I haven’t changed how I feel. Saying “Web browsers are for viewing documents” is silly to me at this point, and suggesting it’s “the biggest wrong turn in the history of computing” feels like “your career in web development is invalid” and when people dig at what I do, I notoriously don’t take it well.

The point is silly anyway. Ken posted this on Twitter-dot-com, and then followed up with a link to job postings. I hope we all can see that Ken was literally leveraging the not-just-a-document-viewer nature of the web to help spread his message and help himself. It feels like saying “cars are bad” and then getting in your car to go to the grocery store.

Ship: sailed. The web is incredibly feature-rich far beyond viewing documents. I know the argument is that this was a mistake, not that web browsers aren’t currently capable of more. If that’s true though, what would you have the web do? Start stripping away features? Should we strip browsers down to document viewers? Maybe we just hand the keys to Facebook and we’ll just do whatever they say we should (lolz).

A super-capable open web is excellent. It means we can build things on open standards on the open web rather than things on proprietary technologies in walled gardens. It’s the better place to build things. URLs alone are a reason to build on the web.

There is still nuance to getting it right though. I enjoyed Noam Rosenthal’s baby bear porridge “Should The Web Expose Hardware Capabilities?”, which begins by discussing Alex Russell’s “Platform Adjacency Theory”:

I relate with the author’s passion for keeping the open web relevant, and with the concern that going too slow with enhancing the web with new features will make it irrelevant. This is augmented by my dislike of app stores and other walled gardens. But as a user I can relate to the opposite perspective — I get dizzy sometimes when I don’t know what websites I’m browsing are capable or not capable of doing, and I find platform restrictions and auditing to be comforting.

Maybe we just take it slow and do things carefully. Good slow. Slow, like brisket.

We’re doing that now, if by accident. Google forges ahead extremely quickly. Apple says hold on there, there are security issues here. And a smidge of vice-versa. I’d point out the other forces at work, but I guess we’re kinda down to two major browser vendor players. Not to discount Mozilla, but the choices they make with the web platform don’t affect the momentum of the web all that much at the moment.

The post The Web is for More Than Document Viewing appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

I Saw Two Mega Menus Today…

Css Tricks - Tue, 02/02/2021 - 11:56am

One was the footer of an (older) U.S. Government website:

The other was the navigation for AWS services from the AWS Console:

It’s weird how much they use the word “Amazon” and “AWS” when you’re literally logged into AWS.

Both of them have that vibe of: holy crap we have a lot of stuff, I guess we’ll just make a massive grid of links to it all.

The difference is the AWS Console one has a search bar at the top of it. Its primary function is finding things in that menu (but it does search the wider site as well):

They also have a “favorites” UI for saving the ones you use the most.

The “search a list of things already on the page” idea reminds me of that classic jQuery contains selector. Please allow me:

CodePen Embed Fallback

The post I Saw Two Mega Menus Today… appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Dynamically Switching From One HTML Element to Another in Vue

Css Tricks - Tue, 02/02/2021 - 5:46am

A friend once contacted me asking if I had a way to dynamically change one HTML element into another within Vue’s template block. For instance, shifting a <div> element to a <span> element based on some criteria. The trick was to do this without relying on a series of v-if and v-else code.

I didn’t think much of it because I couldn’t see a strong reason to do such a thing; it just doesn’t come up that often. Later that same day, though, he reached out again and told me he learned how to change element types. He excitedly pointed out that Vue has a built-in component that can be used as a dynamic element in the very way that he needed.

This small feature can keep code in the template nice and tidy. It can reduce v-if and v-else glut down to a smaller amount of code that’s easier to understand and maintain. This allows us to use methods or computed methods to create nicely-coded, and yet more elaborate, conditions in the script block. That’s where such things belong: in the script, not the template block.

I had the idea for this article mainly because we use this feature in several places in the design system where I work. Granted it’s not a huge feature and it is barely mentioned in the documentation, at least as far as I can tell. Yet it has potential to help render specific HTML elements in components.

Vue’s built-in <component> element

There are several features available in Vue that allow for easy dynamic changes to the view. One such feature, the built-in <component> element, allows components to be dynamic and switched on demand. In both the Vue 2 and the Vue 3 documentation, there is a small note about using this element with HTML elements; that is the part we shall now explore.

The idea is to leverage this aspect of the <component> element to swap out common HTML elements that are somewhat similar in nature; yet with different functionality, semantics, or visuals. The following basic examples will show the potential of this element to help with keeping Vue components neat and tidy.

Button or link?

Buttons and links are often used interchangeably, but there are big differences in their functionality, semantics, and even visuals. Generally speaking, a button (<button>) is intended for an internal action in the current view tied to JavaScript code. A link (<a>), on the other hand, is intended to point to another resource, either on the host server or an external resource; most often web pages. Single page applications tend to rely more on the button than the link, but there is a need for both.

Links are often styled as buttons visually, much like Bootstrap’s .btn class that creates a button-like appearance. With that in mind, we can easily create a component that switches between the two elements based on a single prop. The component will be a button by default, but if an href prop is applied, it will render as a link.

Here is the <component> in the template:

<component :is="element" :href="href" class="my-button" > <slot /> </component>

This bound is attribute points to a computed method named element and the bound href attribute uses the aptly named href prop. This takes advantage of Vue’s normal behavior that the bound attribute does not appear in the rendered HTML element if the prop has no value. The slot provides the inner content regardless whether the final element is a button or a link.

The computed method is simple in nature:

element () { return this.href ? 'a' : 'button'; }

If there’s an href prop,. then an <a> element is applied; otherwise we get a <button>.

<my-button>this is a button</my-button> <my-button href="https://www.css-tricks.com">this is a link</my-button>

The HTML renders as so:

<button class="my-button">this is a button</button> <a href="https://www.css-tricks.com" class="my-button">this is a link</a>

In this case, there could be an expectation that these two are similar visually, but for semantic and accessibility needs, they are clearly different. That said, there’s no reason the two outputted elements have to be styled the same. You could either use the element with the selector div.my-button in the style block, or create a dynamic class that will change based on the element.

The overall goal is to simplify things by allowing one component to potentially render as two different HTML elements as needed — without v-if or v-else!

Ordered or unordered list?

A similar idea as the button example above, we can have a component that outputs different list elements. Since an unordered list and an ordered list make use of the same list item (<li>) elements as children, then that’s easy enough; we just swap <ul> and <ol>. Even if we wanted to have an option to have a description list, <dl>, this is easily accomplished since the content is just a slot that can accept <li> elements or <dt>/<dd>combinations.

The template code is much the same as the button example:

<component :is="element" class="my-list" > <slot>No list items!</slot> </component>

Note the default content inside the slot element, I’ll get to that in a moment.

There is a prop for the type of list to be used that defaults to <ul>:

props: { listType: { type: String, default: 'ul' } }

Again, there is a computed method named element:

element () { if (this.$slots.default) { return this.listType; } else { return 'div'; } }

In this case, we are testing if the default slot exists, meaning it has content to render. If it does, then the the list type passed through the listType prop is used. Otherwise, the element becomes a <div> which would show the “No list items!” message inside the slot element. This way, if there are no list items, then the HTML won’t render as a list with one item that says there are no items. That last aspect is up to you, though it is nice to consider the semantics of a list with no apparent valid items. Another thing to consider is the potential confusion of accessibility tools suggesting this is a list with one item that just states there are no items.

Just like the button example above, you could also style each list differently. This could be based on selectors that target the element with the class name, ul.my-list. Another option is to dynamically change the class name based on the chosen element.

This example follows a BEM-like class naming structure:

<component :is="element" class="my-list" :class="`my-list__${element}`" > <slot>No list items!</slot> </component>

Usage is as simple as the previous button example:

<my-list> <li>list item 1</li> </my-list> <my-list list-type="ol"> <li>list item 1</li> </my-list> <my-list list-type="dl"> <dt>Item 1</dt> <dd>This is item one.</dd> </my-list> <my-list></my-list>

Each instance renders the specified list element. The last one, though, results in a <div> stating no list items because, well, there’s no list to show!

One might wonder why create a component that switches between the different list types when it could just be simple HTML. While there could be benefits to keeping lists contained to a component for styling reasons and maintainability, other reasons could be considered. Take, for instance, if some forms of functionality were being tied to the different list types? Maybe consider a sorting of a <ul> list that switches to a <ol> to show sorting order and then switching back when done?

Now we’re controlling the elements

Even though these two examples are essentially changing the root element component, consider deeper into a component. For instance, a title that might need to change from an <h2> to an <h3> based on some criteria.

If you find yourself having to use ternary solutions to control things beyond a few attributes, I would suggest sticking with the v-if. Having to write more code to handle attributes, classes, and properties just complicates the code more than the v-if. In those cases, the v-if makes for simpler code in the long run and simpler code is easier to read and maintain.

When creating a component and there’s a simple v-if to switch between elements, consider this small aspect of a major Vue feature.

Expanding the idea, a flexible card system

Consider all that we’ve covered so far and put it to use in a flexible card component. This example of a card component allows for three different types of cards to be placed in specific parts of the layout of an article:

  • Hero card: This is expected to be used at the top of the page and draw more attention than other cards.
  • Call to action card: This is used as a line of user actions before or within the article.
  • Info card: This is intended for pull quotes.

Consider each of these as following a design system and the component controls the HTML for semantics and styling.

CodePen Embed Fallback

In the example above, you can see the hero card at the top, a line of call-to-action cards next, and then — scrolling down a bit — you’ll see the info card off to the right side.

Here is the template code for the card component:

<component :is="elements('root')" :class="'custom-card custom-card__' + type" @click="rootClickHandler"> <header class="custom-card__header" :style="bg"> <component :is="elements('header')" class="custom-card__header-content"> <slot name="header"></slot> </component> </header> <div class="custom-card__content"> <slot name="content"></slot> </div> <footer class="custom-card__footer"> <component :is="elements('footer')" class="custom-card__footer-content" @click="footerClickHandler"> <slot name="footer"></slot> </component> </footer> </component>

There are three of the “component” elements in the card. Each represents a specific element inside the card, but will be changed based on what kind of card it is. Each component calls the elements() method with a parameter identifying which section of the card is making the call.

The elements() method is:

elements(which) { const tags = { hero: { root: 'section', header: 'h1', footer: 'date' }, cta: { root: 'section', header: 'h2', footer: 'div' }, info: { root: 'aside', header: 'h3', footer: 'small' } } return tags[this.type][which]; }

There are probably several ways of handing this, but you’ll have to go in the direction that works with your component’s requirements. In this case, there is an object that keeps track of HTML element tags for each section in each card type. Then the method returns the needed HTML element based on the current card type and the parameter passed in.

For the styles, I inserted a class on the root element of the card based on the type of card it is. That makes it easy enough to create the CSS for each type of card based on the requirements. You could also create the CSS based on the HTML elements themselves, but I tend to prefer classes. Future changes to the card component could change the HTML structure and less likely to make changes to the logic creating the class.

The card also supports a background image on the header for the hero card. This is done with a simple computed placed on the header element: bg. This is the computed:

bg() { return this.background ? `background-image: url(${this.background})` : null; }

If an image URL is provided in the background prop, then the computed returns a string for an inline style that applies the image as a background image. A rather simple solution that could easily be made more robust. For instance, it could have support for custom colors, gradients, or default colors in case of no image provided. There’s a large number of possibilities that his example doesn’t approach because each card type could potentially have their own optional props for developers to leverage.

Here’s the hero card from this demo:

<custom-card type="hero" background="https://picsum.photos/id/237/800/200"> <template v-slot:header>Article Title</template> <template v-slot:content>Lorem ipsum...</template> <template v-slot:footer>January 1, 2011</template> </custom-card>

You’ll see that each section of the card has its own slot for content. And, to keep things simple, text is the only thing expected in the slots. The card component handles the needed HTML element based solely on the card type. Having the component just expect text makes using the component rather simplistic in nature. It replaces the need for decisions over HTML structure to be made and in turn the card is simply implemented.

For comparison, here are the other two types being used in the demo:

<custom-card type="cta"> <template v-slot:header>CTA Title One</template> <template v-slot:content>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</template> <template v-slot:footer>footer</template> </custom-card> <custom-card type="info"> <template v-slot:header>Here's a Quote</template> <template v-slot:content>“Maecenas ... quis.”</template> <template v-slot:footer>who said it</template> </custom-card>

Again, notice that each slot only expects text as each card type generates its own HTML elements as defined by the elements() method. If it’s deemed in the future that a different HTML element should be used, it’s a simple matter of updating the component. Building in features for accessibility is another potential future update. Even interaction features can be expanded, based on card types.

The power is in the component that’s in the component

The oddly named <component> element in Vue components was intended for one thing but, as often happens, it has a small side effect that makes it rather useful in other ways. The <component> element was intended to dynamically switch Vue components inside another component on demand. A basic idea of this could be a tab system to switch between components acting as pages; which is actually demonstrated in the Vue documentation. Yet it supports doing the same thing with HTML elements.

This is an example of a new technique shared by a friend that has become s surprisingly useful tool in the belt of Vue features that I’ve used. I hope that this article carries forward the ideas and information about this small feature for you to explore how you might leverage this in your own Vue projects.

The post Dynamically Switching From One HTML Element to Another in Vue appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

“Cancelable” Smooth Scrolling

Css Tricks - Mon, 02/01/2021 - 1:51pm

Here’s the situation: Your site offers a “scroll back to top” button, and you’ve implemented smooth scrolling. As the page scrolls back to the top, users see something that catches their eye and they want to stop the scrolling, so they do a smidge of a scroll on the mouse wheel,, trackpad, or whatever. That’s what I mean by cancellable. Without any further action, the scroll event goes to the destination. Cancellable means you can stop it with a subsequent scroll. I find the cancellable behavior better UX, although I have no data to back that up.

I’m finding some discrepancies between browsers, as well as between CSS and JavaScript on how this all works.

Scroll down on this demo and give it a shot:

CodePen Embed Fallback

Here’s what I experienced on the browsers I have easy access to:

CSS Smooth ScrollJavaScript Smooth ScrollChromeCancellable (Speed: Slowish)Not CancellableFirefoxCancellable (Speed: Very Fast!)Cancellable (Speed: Fast!)SafariNo Smooth ScrollingNo Smooth ScrollingEdgeCancellable (Speed: Fast)Not CancellableiOSNo Smooth ScrollingNo Smooth Scrolling

If it was up to me, I’d:

  • make smooth scroll actions triggered either through CSS or JavaScript cancellable.
  • define “cancellable” because it isn’t really the right word. Maybe “interrupted”? Or “controlled”? Ideas welcome!
  • make the speed controllable, or if not, attempt to get browsers to agree on a medium-ish speed (that stays consistent regardless of scroll distance).
  • make Safari have it. Smooth scrolling makes things like carousels without JavaScript very practical, and that’s great, particularly on mobile where iOS Safari is forced on Apple devices.

The post “Cancelable” Smooth Scrolling appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Open Web Docs

Css Tricks - Mon, 02/01/2021 - 1:51pm

Robert Nyman:

Open Web Docs was created to ensure the long-term health of web platform documentation on de facto standard resources like MDN Web Docs, independently of any single vendor or organization. Through full-time staff, community management, and our network of partner organizations, we enable these resources to better maintain and sustain documentation of core web platform technologies. Rather than create new documentation sites, Open Web Docs is committed to improving existing platforms through our contributions. 

Well that’s… awesome. I know a lot of people were worried about the long-term health of MDN after the Mozilla layoffs, even though they have made some strong moves.

But wait, hasn’t this been tried before with Web Platform Docs? There is a big difference:

Q: Is this the same as Web Platform Docs was?

A: No, Web Platform Docs was a new documentation platform, whereas this is aimed at contributing to existing platforms.

Working through my mental checklist of things I would think this needs to succeed… does it have leadership? Does it have a plan? ✅

Florian Scholz joined the project in November 2020 as our Content Lead, working with stakeholders to define initial workstreams. Our 2021 priorities include working with Mozilla’s MDN writers and engineers to support the recent infrastructure transition and to prioritize and move forward with key documentation work, developing a community of contributors around core web technology documentation, browser compatibility data, and improving JavaScript documentation.

Does it have a team? ✅ Looks like 11 already and new hiring is happening.

Does it have money? ✅

That’s a good chunk of change, and covers that whole first year’s budget (Nov-Nov). Here’s hoping the big contributors keep that contribution up annually. But even if they don’t, I imagine a ton of good will come from just one year of this much focus and investment.

Direct Link to ArticlePermalink

The post Open Web Docs appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Figma Crash Course

Css Tricks - Mon, 02/01/2021 - 10:13am

Totally free course from Pablo Stanley. Can’t beat that.

Figma is just blowing up, and for good reason. It’s good software aligned with what digital designers need. It’s fast. It’s on the web, so you can’t lose stuff and don’t need to figure out a storage strategy. It’s useful beyond designers directly, as the feedback mechanisms are nice and sharing is so easy. The collaborative team features are straightforward and what people expect out of software today. Godspeed.

I also love how it incorporates newfangled web tech so well. I just used the Remove BG plugin in Figma the other day and was like: nice.

Direct Link to ArticlePermalink

The post Figma Crash Course appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Syndicate content
©2003 - Present Akamai Design & Development.