From efdabbb582f225229df9afffe780f6b7a2e29de9 Mon Sep 17 00:00:00 2001 From: James Steinbach Date: Mon, 29 Jul 2024 16:20:55 -0600 Subject: [PATCH 1/5] include misc drafts --- ...17-10-17-animating-background-gradients.md | 56 ++++++ .../2018-02-02-writing-postcss-plugin.md | 186 ++++++++++++++++++ .../2018-06-21-work-life-balance-dockyard.md | 88 +++++++++ .../2019-04-16-css-selector-last-row.md | 105 ++++++++++ .../2019-05-03-just-details-no-devil.md | 95 +++++++++ ...020-03-02-accessible-loading-indicators.md | 142 +++++++++++++ 6 files changed, 672 insertions(+) create mode 100644 src/_drafts/2017-10-17-animating-background-gradients.md create mode 100644 src/_drafts/2018-02-02-writing-postcss-plugin.md create mode 100644 src/_drafts/2018-06-21-work-life-balance-dockyard.md create mode 100644 src/_drafts/2019-04-16-css-selector-last-row.md create mode 100644 src/_drafts/2019-05-03-just-details-no-devil.md create mode 100644 src/_drafts/2020-03-02-accessible-loading-indicators.md diff --git a/src/_drafts/2017-10-17-animating-background-gradients.md b/src/_drafts/2017-10-17-animating-background-gradients.md new file mode 100644 index 000000000..92cf27373 --- /dev/null +++ b/src/_drafts/2017-10-17-animating-background-gradients.md @@ -0,0 +1,56 @@ +--- +title: Animating Background Gradients to Make Your PWA More Native +--- + +

Right now, you can’t transition a gradient in CSS. This is because the various gradient syntaxes (linear-gradient, radial-gradient, repeating-linear-gradient, and conic-gradient) are all values of the background-image property. CSS doesn’t currently allow transitions or animations on background-image, thus gradient can’t be transitioned. Behold:

+

See the pen Transitioning Gradient: Background Transition on CodePen.

+

This is a pretty frustrating limitation. Gradients are made of 3 parts: direction (linear) or position (radial), color values, and color stops. All those values are numbers: a browser should be mathematically capable of transitioning them. Some browsers recently started transitioning between background images in url() so it’s unfortunate that browsers won’t transition gradients now.

+

As we built a recent progressive web app, we had to work around this limitation. As a user scrolls through a timeline of tide data, a gradient representing the time of day transitions through gradients with colors representing night, dawn, day, dusk, and a few in-between phases. In order to give our app that native feel of smooth transitions, we had to get clever.

+

Previous Tricks

+

Some people have figured out some sneaky tricks for pretending to animate a gradient in the background. Let’s look at a few of them quickly:

+

Move the Background

+

If your gradient needs to move, you can draw it larger than the element that uses it and transition the background-position property. (Yes, I know this is not a performant transition: I’m not recommending it, just acknowledging that it’s a working hack.)

+

See the pen Transitioning Gradient: Background Position on CodePen.

+

Move a Pseudo-Element

+

This is a modification of the trick above, but instead of transitioning background-position, it uses the ::before pseudo-element, makes it twice as tall as the containing element, and then transitions transform for a very performant animation. You get the same visual effect, but it’s much nicer on your device’s processor.

+

See the pen Transitioning Gradient: Pseudo-Element on CodePen.

+

Use an Overlay

+

This fits a separate use case. In this method, we don’t get the visual effect of a “moving” background. Rather, one color changes. In this instance, we create a gradient that fades to transparent in background-image, then transition the background-color behind it. Only one of the colors changes, but if the entire background-gradient is semi-transparent, the entire gradient appears to change color. If you use mix-blend-mode or background-blend-mode (and blending is actually supported in your users’ browsers), you can create some really interesting effects with color blending.

+

See the pen Transitioning Gradient: Pseudo-Element Overlay on CodePen.

+

A New(er) Trick

+

None of these techniques matched our use case, however. We needed to transition both colors in the gradient without any visible motion. We found a good solution using an SVG mask.

+

We start by putting an actual SVG into the markup as the immediate child of the element that we want the gradient to cover. Let’s look at that SVG and talk through the code it contains:

+ +```xml + + + + + + + + + + + + +``` + +

In the SVG, we’re using viewBox="0 0 1 1" so that all the internal coordinates are based off of a 1px relative grid. With CSS, we can stretch the SVG to cover its parent; this viewBox just simplifies our math.

+

When the SVG stretches differently from its 1:1 aspect ratio, we want to keep it scaled nicely. That’s what we gain from preserveAspectRatio="xMidYMid slice". The SVG’s internal elements will expand to cover the SVG’s rendered size. If you’re familiar with CSS, this is the equivalent of telling the SVG’s contents to behave like background-size: cover and background-position: center. If you’re going with a horizontal or angled gradient, you may need to adjust that value to get the desired visual effect.

+

The defs element in an SVG contains elements that can be used to mask or fill other elements but aren’t visually rendered on their own. Our first element there is mask that contains a rect - the mask will be used on a visible element outside of defs; the rect ensures that the mask takes up space.

+

Then we find a linearGradient element: we’ll draw a white gradient from opaque to transparent with two stop elements. For the mask to work as desired, their stop-color needs to be white, but you can modify them or add more as desired to create more complex stripe patterns.

+

The mask is told to use this linearGradient to create its masking pattern.

+

Outside of defs we have a lone rect - this is the only SVG element visible to users. This element uses mask="url(#mask)" to create a gradient mask while still allowing us to transition its fill color with CSS.

+

This brings us to CSS. Here we can transition or animate the background-color on the element itself, and the fill color on the visible rectangle. Ta-da! Both colors in the gradient transitioning together!

+

See the pen Transitioning Gradient: SVG Mask on CodePen.

+

Gotchas

+

Of course, no CSS trick ever works perfectly. We ran into a few snags and trade-offs implementing this technique.

+

Performance

+

It is true, color (background, border, or fill) isn’t the most performant property to transition, but it’s not the worst either. We’re including will-change to get some GPU acceleration help and improve the transition. Also, we’re not going to transition 318 layers simultaneously! In our app, we’re only changing a single instance of this gradient (2 layers). If you use this technique with 1 dozen masked rectangles, your mileage will certainly vary - good luck.

+

Banding

+

Depending on the mask you draw, you may see visible banding on the gradient. We found this when the gradient was rotated 45°. This is a more complicated issue to solve, but here are the things that helped us improve the visual rendering of the gradient.

+

We used some CSS on the SVG to smooth it out: filter: blur(3px). Different values will give you more or less improvement on the banding issue, but they introduce a performance hit. We started with the blur value at 10px but transitioning the color inside the blurred SVG ended up being really costly and dropped the transition performance well below 60FPS. We dropped the blur down to 3px - now the banding was a bit more noticeable (especially on non-retina screens) but the transition performance was good again.

+

Using a CSS filter also introduced a new constraint: the blur filter starts from the edge of the container, so the outer 3px of the SVG are “blurred in” and the gradient doesn’t extend edge-to-edge properly. Our solution to this was to change the absolute position values on the SVG: -3px all around. This required overflow: hidden on the containing element. In this situation that was the page background, so the overflow restriction didn’t harm anything, but that’s not true in every situation.

+

Conclusion

+

We can’t currently transition a CSS gradient, but by using an SVG mask, we can create a gradient and transition all its colors. I’d love to see browser vendors enable transitions on gradients at some point in the future. That transition would likely function similar to clip-path: polygon() and SVG path values: browsers require those values to have the same number of points in order to transition them. That would be a reasonable limitation on transitioned gradients. Until then, however, an SVG mask provides the most performant way to transition multiple colors in a gradient.

diff --git a/src/_drafts/2018-02-02-writing-postcss-plugin.md b/src/_drafts/2018-02-02-writing-postcss-plugin.md new file mode 100644 index 000000000..c4e0e461a --- /dev/null +++ b/src/_drafts/2018-02-02-writing-postcss-plugin.md @@ -0,0 +1,186 @@ +--- +title: Writing Your First PostCSS Plugin +--- + +

Why Use PostCSS?

+

You may already be using Sass or Less to add logic to your CSS workflow: variables, if/else statements, functions, and mixins. However, there are some limitations to those preprocessors. What if you need to add a CSS property or two based on the presence of other CSS properties?

+

For example, we’ve worked a lot on progressive web apps here at DockYard lately. We want that nice native-feeling elastic/inertia scrolling whenever we have an element with overflow: scroll (or overflow-x / overflow-y). Everywhere we make an element scrollable, we’d need to add -webkit-overflow-scrolling: touch. Preprocessors don’t have a way to detect what properties are in a given selector block, so we’d need a verbose mixin solution. Additionally, we don’t use a preprocessor on every project, so we needed a PostCSS solution.

+

For the sake of comparison, here’s how we might implement a Sass mixin for this scrolling behavior:

+ +```scss +@mixin overflow-scroll($direction: false) { + $property: if($direction, 'overflow-#{$direction}', 'overflow'); + + #{$property}: overflow; + -webkit-overflow-scrolling: touch; +} +``` + +

See that mixin in use on Sassmeister.

+

This approach works, but it has some significant shortcomings. First, you’re not writing spec CSS anymore: you’re writing a more verbose abstraction. Any dev who comes to this codebase has to learn another abstraction. Second, this isn’t well-automated. If you forget to use the mixin, you don’t get the extra property.

+

PostCSS, on the other hand, automates this fully with no need for a written abstraction. A PostCSS plugin can find any selector block with a scrolling overflow and insert the additional property automatically.

+

What Is PostCSS?

+

Before we get into the actual plugin writing process, let’s understand what PostCSS is. PostCSS allows us to manipulate our CSS with JavaScript functions. It does 3 things to accomplish that:

+
    +
  1. PostCSS turns your CSS file into a JS object. +
  2. +
  3. PostCSS plugins can loop through the object and add/remove/modify selectors and properties. +
  4. +
  5. PostCSS converts that object back to a CSS file. +
  6. +
+

If you’re interested in practical value of PostCSS, you can read more about why DockYard transitioned to PostCSS and our PostCSS package: Narwin-Pack.

+

There are dozens of PostCSS plugins already available, openly maintained, and published to npm.

+

What if you have a use case that’s not covered by an existing plugin? (Perhaps like the one we discussed above?)

+

Writing a PostCSS Plugin

+

The PostCSS team has done a great job removing obstacles to writing your own plugin. The rest of this tutorial assumes a few things about your skill level:

+
    +
  1. that you’re comfortable with git and the command line, +
  2. +
  3. that you can write JavaScript functions, and +
  4. +
  5. that you have node installed and know how to install npm modules +
  6. +
+

Clone the PostCSS Plugin Boilerplate Repo

+

Head over to your terminal and clone the PostCSS Plugin Boilerplate repo

+ +```sh +$ git clone git@github.com:postcss/postcss-plugin-boilerplate.git +``` + +

Next, run the wizard script from that repo:

+ +```sh +$ node ./postcss-plugin-boilerplate/start +``` + +

This script will ask you several questions in your terminal. It’ll pull your name and email address from your local git profile (if you’ve set that up), and then ask you for your Github username.

+

Next, you’ll choose your plugin name. It’ll begin with postcss- and you’ll complete the name. The wizard will then ask you to finish a sentence describing what your plugin will do. Finally, it’ll start a comma-separated list of tags for you to complete.

+

'PostCSS Plugin Boilerplate wizard'

+

Once you’ve finished this setup, you’ll have a boilerplate directory: the wizard created it with the same name that you chose for your plugin while answering the script’s questions. Let’s head over to that directory:

+ +```sh +$ cd postcss-test-plugin +``` + +

In it, you’ll find some familiar components of a node-based project: index.js, package.json, a node_modules directory. You’ll put your logic in index.js: the functions that manipulate the CSS. If you have any other node module dependencies for your plugin, package.json will manage them and install them in node_modules.

+

The Boilerplate Code in index.js

+

Let’s start by looking at the boilerplate code provided in index.js:

+ +```js +var postcss = require('postcss'); +``` + +

The first thing it does is grab the necessary prerequisite: the PostCSS library itself. The code that follows relies on having access to PostCSS.

+ +```js +module.exports = postcss.plugin('postcss-test-plugin', function (opts) { + opts = opts || {}; + // Work with options here + return function (root, result) { + // Transform CSS AST here + }; +}); +``` + +

This block of code is the part that actually contains instructions for manipulating your CSS.

+

The first thing we’ll need to do is walk through all the declaration blocks in the stylesheet. The root parameter inside the return function has a method for that: .walkRules().

+

Looping Through Each Selector Block

+

We’ll upgrade the boilerplate with .walkRules() to loop through every declaration block and let us access the styles in it:

+ +```js +root.walkRules(function(rule) { + // We'll put more code here in a moment… +}); +``` + +

Now that we’re walking through each selector block, we need to see if it contains a overflow property. To access those properties, we’ll use the .walkDecls() method that’s part of the rule passed to the function above.

+

Looping Through Each Property

+ +```js +rule.walkDecls(function(decl) { + // We work with each `decl` object here. +}); +``` +

Inside this loop, decl is an object representing a style declaration. It contains data about the property-value pair as well as some methods for manipulating it. The two most important things for our case are decl.prop (the property name) and decl.value (the property value).

+

Finding Overflow Properties

+

To detect if a decl is overflow-related, we could put an if statement inside this loop: if (decl.prop.indexOf('overflow') === 0), but there’s a more efficient way to do that. PostCSS lets us filter for specific properties in the .walkDecls() method. You can find this in the PostCSS API Documentation. We don’t need that if statement if we filter for the overflow property like this:

+ +```js +rule.walkDecls('overflow', function(decl) { + // We work with the `decl` object here. +}); +``` + +

This isn’t quite right, however. It’s only going to find the overflow property. If we want to account for overflow-x and overflow-y also (and we do), we need to adjust that filter a bit. This prop parameter doesn’t take an array of property names (I tried ['overflow', 'overflow-x', 'overflow-y'], but no luck). To match multiple properties, we’ll have to use a bit of RegEx: /^overflow-?/. Here’s a quick explanation for that syntax: the ^ means the property name has to start with overflow; the -? means “there might or might not be a - after the word overflow. Notice that we don’t use '' around the regex. This brings us to:

+ +```js +rule.walkDecls(/^overflow-?/, function(decl) { + // We work with the `decl` object here. +}); +``` + +

Preventing Duplicate Properties

+

It’s taken a bit of time, but now we’re almost there: this code will loop through all the selector blocks in our stylesheet, then loop through all the overflow-related properties in those selectors. All that’s left to do is insert our property. The next block of code will check to see if the overflow-related property’s value is scroll and if so, add the property that makes it feel more native.

+ +```js +if (decl.value === 'scroll') { + rule.append({ + prop: '-webkit-overflow-scrolling', + value: 'touch' + }); +} +``` + +

In this case, we are resorting to an if statement. The loop we wrote a moment ago filtered properties so this function only runs on decl objects where the property starts with overflow-?. Now, if decl.value is scroll, we’ll add a property-value pair to the parent rule object. We’re almost done now. It’s possible that someone might have already included the -webkit-overflow-scrolling property. We don’t want to duplicate it. PostCSS has a function that lets us check to see if a given property is already in a selector block:

+ +```js +var hasTouch = rule.some(function(i) { + return i.prop === '-webkit-overflow-scrolling'; +}); +if (!hasTouch) { + rule.append({ + prop: '-webkit-overflow-scrolling', + value: 'touch' + }); +} +``` + +

Now we’ve got a better function: if a developer intentionally put the -webkit-overflow-scrolling where it was needed, we won’t duplicate it.

+

Conclusion

+

In just 20 lines of code, we’ve created a useful PostCSS plugin.

+ +```js +var postcss = require('postcss'); +module.exports = postcss.plugin('postcss-test-plugin', function() { + return function(root) { + root.walkRules(function(rule) { + rule.walkDecls(/^overflow-?/, function(decl) { + if (decl.value === 'scroll') { + var hasTouch = rule.some(function(i) { + return i.prop === '-webkit-overflow-scrolling'; + }); + if (!hasTouch) { + rule.append({ + prop: '-webkit-overflow-scrolling', + value: 'touch' + }); + } + } + }); + }); + }; +}); +``` + +

Of course, there are more complications that we’d consider for production purposes:

+ +

But, all things considered, we did put together a working plugin pretty quickly. Hopefully, you’re able to take this walk-through and put together your own PostCSS plugins in the future!

diff --git a/src/_drafts/2018-06-21-work-life-balance-dockyard.md b/src/_drafts/2018-06-21-work-life-balance-dockyard.md new file mode 100644 index 000000000..50783fb74 --- /dev/null +++ b/src/_drafts/2018-06-21-work-life-balance-dockyard.md @@ -0,0 +1,88 @@ +--- +title: “Work-Life Balance” at DockYard +--- + +

One of the most important things to me about my job is work-life balance, and when I joined DockYard last year, I wanted to make sure that they were a company that valued that balance for their employees. If you’re considering applying to DockYard, this post will help you better understand our company culture. Really, if you’re applying anywhere at all, this post may help you evaluate that company as well. I recently surveyed my coworkers at DockYard to get their opinions about work-life balance: what it is, how important it is, and how DockYard culture protects it.

+

I’ve barely started this post, however, and I can already sense some people objecting to the label “work-life balance.” I understand that. Let’s talk about pushback to that label and see if we can find some common ground to move forward from. Most of the popular articles titled “I don’t believe in work-life balance” boil down to semantics and/or word choice, or definitions of the label itself. Some people don’t like the word “balance” and prefer “integration.” Others object to pitting “work” against the rest of “life” and just tell people to “balance their roles” or “manage their energy.” Still others just claim it’s “impossible” and move along (usually maintaining a high-pressure, heavy workload, but making themselves feel better about it, maybe). Here’s what almost everyone agrees about though: no matter what you call it, most of us have more things going on in “life” than just “work,” and we want to make sure that the “work” doesn’t overwhelm the other parts of “life.” In simpler words, we want to work somewhere we’re respected, not overworked.

+

What Does Work-Life Balance Mean to You?

+

Rather than trying to create and defend one canonical definition of “work-life balance,” let’s embrace the idea that it means different things to different people. (Also, is it cool if I abbreviate it to WLB? That’ll save me a lot of keystrokes, thanks!) So let’s see what some DockYarders said WLB means to them:

+

“Work life balance is about having adequate time outside of your full-time work commitment to refresh your mind and manage other important aspects of your life.”

+
+

“Work-life balance is being able to have separation between your work and all of the things you do outside of work, and focused, intentional time for each.”

+
+

“Work life balance is the ability to be working on something I’m passionate about, and at the same time being able to disconnect to gain perspective and appreciate the world around me.”

+
+

“A good work-life balance is the ability to work a reasonable amount of time with ability to stop afterward and disconnect from that work.”

+
+

The common thread through those quotes is that “work” and “the rest of life” are both important, and should have their own boundaries so they don’t infringe on each other. Well, “the rest of life” is a pretty broad category: what does that mean to DockYarders? Here are our values that WLB protects (participants could choose more than 1):

+ +

Other answers provided also included sports, volunteering, and health/wellness. DockYard is by no means a monoculture: our team members have a wide variety of interests and values outside of work. One thing we have in common, however, is a deep appreciation for the freedom to enjoy our time away from the keyboard. When I asked “how important is WLB to you?” (scale of 1-5), 80% of the survey takers chose 5 (the average was 4.6).

+

Who Benefits from Work-Life Balance?

+

Maybe you’ve landed on this post and you’re a manager or someone trying to build a company culture that values WLB. A common misconception is that it just benefits “workers.” Some people believe that companies benefit more from squeezing every waking hour out of salaried employees. Let’s return to what we DockYarders say about WLB:

+

“Work life balance is about having adequate time outside of your full-time work commitment to refresh your mind and manage other important aspects of your life.”

+
+

“Work-life balance is being able to have separation between your work and all of the things you do outside of work, and focused, intentional time for each.”

+
+

“Work life balance is the ability to be working on something I’m passionate about, and at the same time being able to disconnect to gain perspective and appreciate the world around me.”

+
+

“Doing good work in a way that it doesn’t interfere with living your life. Being able to have the freedom to work how and when you want to work so you’re more productive in the time you are working.

+
+

It’s pretty clear that valuing WLB doesn’t diminish our passion for our work at all. It doesn’t mean that we dislike working or are fishing for an excuse to work less. (I’ve run across managers & companies in the past that treated “work-life balance” as cheap excuse/buzzword that employees throw around in order to work less. They’re somehow blissfully unaware how much that dismissal echoes the sentiment of Ebenezer Scrooge’s complaint that Christmas is a “poor excuse every December the 25th to pick a man’s pockets.”)

+

We care deeply about the work we do at DockYard, but we also recognize that without time to refresh, rest, and recharge, our work would suffer. This balance helps both DockYard as a whole and all of us as individual workers: when we have sufficient separation from work, we “gain perspective,” refill our energy to do work we’re “passionate about,” and maintain our ability to devote “focused, intentional time to both”. This means DockYarders can spend our time delivering better work to our clients. The whole company benefits more when all of us show up refreshed and focused than it would if we let hours and pressure spiral out of hand and keep us all in a state of quasi-burnout.

+

How Do Applicants Learn About an Employer’s Work-Life Balance?

+

I asked my coworkers how they evaluate a company’s WLB claims when they’re applying for a position. It’s easy for an employer to say plenty of nice things about work-life balance, but how can applicants cut through marketing-speak and get the real info? Here are some of their techniques:

+

“Ask about what day-to-day is like, and how often the company falls into “fire-drill” mode.”

+
+

“[Ask] if the person interviewing me has taken vacation in the last six months.”

+
+

“Look at the employees social media accounts to get a sense of what they do outside of work.”

+
+

“Try to apply to places you know someone, or check resources like Glassdoor.”

+
+

“Ask each interviewer to describe their work life balance and hear from each person’s perspective.”

+
+

“[Notice how] they talk about it. The more vague, the more likely that it is not taken seriously.”

+
+

“[Ask] whether there is on-call or other ops works outside the normal 9-5. [Ask] whether it is expected that you’ll respond to email/text alert/Slack outside of normal work hours.”

+
+

Two trends emerged in these answers. First, ask some good indirect questions or do some research to see how employees act and feel about their workload. Use Twitter, Glassdoor, or other public sources to see if people really are as happy as a hiring manager or interviewer advertises them to be. Look at how specifically the interviewer discusses WLB policies or practices. The more detailed a policy is, the more likely it is to be carried out successfully. Vague, fluffy opinions about balance that aren’t backed up by concrete policies – those are where companies can hide a lack of balance.

+

Second, don’t be afraid to ask direct questions about the aspects of WLB that matter to you. There’s really no downside to this: if you have a specific need for flexibility and you get shut down or dodged when you ask about it, you just learned an important lesson about that potential employer.

+

Speaking of direct questions and specific answers, let’s wrap up with a look at how DockYard answers WLB questions from applicants.

+

How Does DockYard Treat Work-Life Balance?

+

About 70% of DockYarders who took the survey said that DockYard communicated important WLB info during the interview process itself. About 20% said they got their WLB questions answered through conversations with DockYarders outside the formal interview process. Here are some of the specific details we remember from those interviews and conversations:

+

“Barring rare exceptions, I was told that employee happiness is paramount, even at the expense of extra client work.”

+
+

“I was told that time off from work to recharge and refocus was a priority.”

+
+

“We work a 32-hr billable week, so unlike a product company, work rarely goes home with you.”

+
+

“DockYard communicated a healthy work-life balance through a trusting remote work culture, a commitment to keeping working hours to 8 hours a day to prevent burn-out, routine company retreats that don’t involve any work, and a weekly schedule of 4 client days per week with a “DockYard day” every Friday to work on internal projects, pursue self-guided learning, and have a change of pace from client work.”

+
+

Here at DockYard, we do billable client work Monday through Thursday and every Friday is a “DockYard Day” (time spent on professional development, internal projects, and open source contributions). The way DockYard runs its billable time is designed to keep those 4 days to “normal” ~8 hour days: there’s no benefit to DockYard driving employees to work long days or weekends.

+

In addition to ensuring a sustainable weekly schedule, DockYard also has a fantastic vacation/PTO policy. In addition to normal federal holidays, DockYard closes down for two, week-long company holidays (around Christmas and Independence Day). Employees are given generous yearly PTO time, as well as flexible sick time and parental leave policies. As I interviewed, I was impressed at how DockYard carefully crafted our policies to provide long-term stability and satisfaction for employees. Many developers and designers have experienced workplaces where employees are treated like mere “resources” to be used up and burned out. DockYard is the opposite: our company is designed for sustainable, happy work.

+

I then asked DockYarders how they’ve seen DockYard follow through on those promises. Policies and handbooks are great, but what happens on a daily basis is what really counts.

+

“I cherish the freedom and responsibility of working remotely and it makes me want to reciprocate with value added work.”

+
+

“People are truly encouraged to disconnect on nights, weekends, and on days off. I don’t feel bad or guilty taking time away from work and it makes me more committed to doing great work when I am here.”

+
+

“It’s been the absolute best. Working remotely helps tremendously with this, more so than I was anticipating. …I’ve worked late a handful of days, but that’s rare, and never past 6pm. DockYarders are also very intentional and respectful about pinging folks after hours, prefacing with ‘future you’ so you know it’s not an immediate action item.”

+
+

“I have seen flexibility across the staff when it comes to dealing with childcare, sick children, hours, etc. Everyone is focused on meeting the needs of the client but does so without sacrificing the employees.”

+
+

“I think DockYard undersold the work-life balance prior to me joining. Most companies expect salaried employees to work well beyond 40 hours a week, whereas DockYard encourages its employees to enjoy their evenings and weekends.”

+
+

“I think work-life balance is excellent at DockYard. I rarely need to address work stuff after 6 pm on the weekdays and very few items need attention on the weekends. Working from home also means that I can be flexible with the times I work and take appointments, etc. as necessary.”

+
+

DockYard puts its money where its mouth is: WLB is a real part of our normal company culture. Some employees even felt like the balance was even better in real life than it was advertised to be during the interview/hiring process. We enjoy our weekends and evenings, work-free. Even working from home, work doesn’t absorb our homes. Slack slows down outside of the hours where most of us are online concurrently. Conversations usually happen in a comfortable async way: one employee can ask a question “outside normal work time” without creating pressure for anyone else to answer immediately. WLB isn’t just a buzzword to entice applicants: it really is how we do things here at DockYard.

+

Sound like a place you’d like to work? Apply for one of our open positions!

diff --git a/src/_drafts/2019-04-16-css-selector-last-row.md b/src/_drafts/2019-04-16-css-selector-last-row.md new file mode 100644 index 000000000..a2a13ca4c --- /dev/null +++ b/src/_drafts/2019-04-16-css-selector-last-row.md @@ -0,0 +1,105 @@ +--- +title: CSS Selectors for the Entire Last Row of a Dynamic Grid +--- + +

Allow me to begin by describing a recent layout problem I needed to solve. I was working with a dynamic grid of items: the number of items in the grid was variable (provided by an API payload, not a predictable multiple of the column count). The grid items used margin-bottom to create vertical space (browser-support requirements didn’t allow CSS Grid and row-gap), so items in the bottom row of the grid needed that margin removed (or else there would have been too much space below the grid container). For example, we might be styling a grid of related products with a variable number. A calendar layout would match this use case too, since the final row of “days” would vary by month.

+

I needed a way to select elements in the final row of that grid. Here’s the CSS selector set I used to target elements in the last row.

+

Finding Children that Could Be in the Final Row

+

I started by finding all the elements that could be in the last row. The :nth-last-child() selector makes this possible. The :nth-last-child() behaves just like :nth-child() but calculates all element positions from the end of the parent element, not the beginning.

+

For example, this CSS finds the third element from the end of its parent:

+ +```css +.child:nth-last-child(3) { } +``` + +

This CSS targets every even-numbered child element, starting at the end:

+ +```css +.child:nth-last-child(even) { } +``` + +

This CSS selects the last four children in the parent element:

+ +```css +.child:nth-last-child(-n + 4) { } +``` + +

That’s the pattern we need to select the elements that could be in the final visible row. Here’s how that would work with a responsive grid that expands from two columns to four columns to six columns inside media queries:

+ +```css +/* Under 500px, select the last 2 elements */ +@media (max-width: 499px) { + .child:nth-last-child(-n + 2) { } +} +/* Between 500px - 799px, select the last 4 elements */ +@media (min-width: 499px) and (max-width: 799px) { + .child:nth-last-child(-n + 4) { } +} +/* At 800px & above, select the last 6 elements */ +@media (min-width: 800px) { + .child:nth-last-child(-n + 6) { } +} +``` + +

With these selectors, we’ve targeted any element that could be in that last row of the grid. We still have a small problem however. If the data doesn’t provide enough items to fill the final row, we’ve selected too many items. For example, if the payload from the API returned nine items, and our grid was six columns wide, we’d only have three items in the last row, but this CSS selector would select the final six items. In just a moment, we’ll add another selector that allows us to select only the final three items in that example.

+

Notes: I used strictly limited min-width & max-width media queries because :nth-child() adds specificity that we don’t want to have to override at a new breakpoint.

+

For more information about these selectors, read our DockYard introduction to these pseudo-classes, or MDN’s :nth-child() and :nth-last-child() docs.

+

Finding the First Child in Every Row

+

We can chain multiple pseudo-selectors onto a single CSS selector. This will allow us to find the element that’s possibly in both the final row and the first child of a row. We’ve already got the elements that could be in the final row. Now let’s select any element that’s first in its row. (For this example, we’re assuming six elements per row.)

+

The :nth-child() selector gives us access to every element that’s first in its row, as long as we know the number of columns. (In this example, we’re using six columns.)

+ +```css +.child:nth-child(6n + 1) { } +``` + +

Inside the :nth-child() function, 6n finds every element whose position is divisible by six (0, 6, 12, so on). That’s the last element in every row, so we all + 1 and the selector now targets the next element position (1, 7, 13, so on). We’ve now selected the first element in every row:

+ +

We combine :nth-child(6n + 1) with :nth-last-child(-n + 6), and now we have a single line of CSS that always selects the first element in the last row of content:

+ +```css +.child:nth-child(6n + 1):nth-last-child(-n + 6) +``` + +

There’s one more small adjustment we need to make. We’ve selected the first element in the last row, but we also need all the other elements in the last row. CSS’s ~ selector (general sibling combinator) lets us select all of those elements:

+ +```css +/* first row item & in the last row */ +.child:nth-child(6n + 1):nth-last-child(-n + 6), +/* all the remaining children */ +.child:nth-child(6n + 1):nth-last-child(-n + 6) ~ .child { } +``` + +

The first line in that selector finds just the element that is both “the first child in its row” and “within the last row count of items.” The second line finds all the elements that follow the first element in the final row.

+

For any grid column count, we can substitute the column count for 6, and we can also wrap it in media queries for responsive selectors. Here’s our responsive CSS from earlier, upgraded with our awesome selector:

+ +```css +@media (max-width: 499px) { + .child:nth-child(2n + 1):nth-last-child(-n + 2), + .child:nth-child(2n + 1):nth-last-child(-n + 2) ~ .child { } +} +@media (min-width: 500px) and (max-width: 799px) { + .child:nth-child(4n + 1):nth-last-child(-n + 4), + .child:nth-child(4n + 1):nth-last-child(-n + 4) ~ .child { } +} +@media (min-width: 800px) { + .child:nth-child(6n + 1):nth-last-child(-n + 6), + .child:nth-child(6n + 1):nth-last-child(-n + 6) ~ .child { } +} +``` + +

Here’s a CodePen showing this CSS in action:

+

Note: If you’re into Sass or a similar preprocessor, this repetitive code could be DRYed out with a mixin.

+

A Better Way: Grid

+

Of course, it’s worth pointing out that this exact problem would be a non-issue with CSS Grid. In CSS Grid, we’d use the row-gap property to create space only between rows: there’d be no need to find the final row items to override any spacing or layout CSS.

diff --git a/src/_drafts/2019-05-03-just-details-no-devil.md b/src/_drafts/2019-05-03-just-details-no-devil.md new file mode 100644 index 000000000..20d68c97c --- /dev/null +++ b/src/_drafts/2019-05-03-just-details-no-devil.md @@ -0,0 +1,95 @@ +--- +title: Just Details; No Devil +--- + +

Have you ever needed to code a series of collapsible pieces of content? Maybe some FAQs or some “accordion” components to allow readers to dig more deeply into content that was more relevant for them? In agencies or client-facing work, these might be called accordions, collapsible panels, toggled content, plus/minus menus, or something else.

+

Chances are, you reached for some JavaScript to do that. Maybe you used a definition list (dl) with click events on all the definition term (dt) elements? If you were concerned with accessibility, you might have toggled aria-expanded on each affected definition details (dd) element. At this point, you’re handling a significant amount of complexity: semantic markup, JS event/state handling, accessibility attribute management.

+

What if I told you that browsers can natively handle this collapsed-content behavior?

+

Enter the details element (and its bff: summary):

+

Introducing details

+

The details element is a pretty useful HTML element that handles collapsible content natively.

+

Using details is pretty straightforward: you wrap a details element around any block of HTML content. The browser will collapse that block of content until a user expands it.

+ +```html +
Here’s some content that might not be useful to everyone so we’ve “hidden” it in a details block till a user expands it.
+``` + +

Once a user opens a details block, they’ll be able to read all that hidden content.

+

If you want a specific details block to be open by default, set the open attribute on the opening tag:

+ +```html +<​details open>All of this content will be expanded by default. A user can still collapse this block if they want to.<​/details> +``` + +

Custom Titles with summary

+

By default, browsers give a details element a generic “Details” title. You can customize that with the summary element.

+

Put a summary at the beginning of your details element and Boom! - you’ve got a custom title for your details block.

+ +```html +
+ More information about this topic +

Here’s a lot more information about the topic at hand!

+
+``` + +

Styling details and summary

+

You can style the details and summary elements however you like. Set a border, some padding, whatever your designs call for.

+

Removing the summary icon

+

The summary element is where the marker lives. If you want to get rid of that, there is a prefixed pseudo-element selector: ::-webkit-details-marker. Set that to display: none for WebKit browsers.

+

In Firefox and Edge, it’s a bit different. Change the summary’s display value to anything but its native list-item; then the will be removed.

+ +```css +/* Firefox & Edge */ +summary { + display: block; +} +/* Safari & Chrome */ +::webkit-details-marker { + display: none; +} +``` + +

Styling open and closed states

+

When a details block is open, it has the open attribute that I mentioned earlier. To style it (or its children) based on its state, use details[open].

+ +```css +details[open] { + box-shadow: 0 0 5px black; +} +``` + +

Note: there’s no closed attribute. Any styles you apply without the [open] selector scope will be used on the closed state.

+

JavaScript, Accessibility, and Support

+

No JavaScript required

+

It may seem too good to be true, but in supporting browsers, no JS is needed to make details work. There are a few scenarios that would require JS:

+ +

Accessible by default

+

Since details and summary are native HTML elements, they provide useful semantic information to screen readers.

+

Screen readers will typically read the summary for a collapsed details block (and communicate that it’s collapsed). They’ll also provide an interactive hook for users to open the details block. If the details is already expanded, they’ll read the whole content.

+

I don’t rely on assistive tech to read the web, so I may be unaware of some limitations or drawbacks to using details and summary, but I suspect their assistive tech user experience is at least as good as (if not better than) most JavaScript-dependent accordion solutions.

+

Browser support

+

Unfortunately, details and summary don’t work in IE or Edge. They are supported in Firefox (since 49: 2016), Chrome (since 6: 2011), Safari (since 6: 2012), and Android (since 4: 2011). Check out caniuse data for details and summary.

+

Non-supporting browsers don’t collapse/expand. They show all the contents like a block-level element. This is a very nice bit of progressive enhancement: if a user’s browser doesn’t support details and summary, they can still read all the content and custom styles are still applied.

+

You’ll want to be careful to remove any interactive affordances in IE and Edge: don’t show a user toggle icons for something that won’t move. You can include a JS polyfill if the behavior is necessary.

+

It’s unlikely IE11 will be getting any updates on this front, but there’s hope for Edge! If this is important to you, please cast a vote a vote for Edge to support details and summary..

+

Additional Resources

+

If you’d like to continue learning about details and summary, here’s a list of resources that will be helpful to you:

+ diff --git a/src/_drafts/2020-03-02-accessible-loading-indicators.md b/src/_drafts/2020-03-02-accessible-loading-indicators.md new file mode 100644 index 000000000..6a05bdee7 --- /dev/null +++ b/src/_drafts/2020-03-02-accessible-loading-indicators.md @@ -0,0 +1,142 @@ +--- +title: Accessible Loading Indicators—with No Extra Elements! +--- + +

It’s almost expected that web apps (no matter what framework or language they use!) will need some time to process their response to user actions. Those delays can be tied to interactions like submitting forms, changing routes, loading content from an API, and uploading files, to name a few. Unfortunately, some apps seem to expect users to instinctively sit still and wait during these asynchronous functions: they don’t bother providing any visual or semantic clues that the app is busy!

+

We’re going to look at some HTML & CSS that allow us to communicate to users that part of the page is waiting on an async response. These features will communicate to both sighted and screen reader users that the app is busy and they need to wait.

+

In this post, we’ll gradually improve a block of sample code. For this example, we’ll build a random news article container. Every time a user clicks the “Show New Post” button, the container will load a new article.

+ +```html +
+
+

+ +
+
+
+``` + +

Accessible Loading Indicator: ARIA Attributes

+

aria-live

+

There’s a little-known ARIA attribute that tells screen readers and other assistive tech that part of the app contains dynamic content: aria-live. The aria-live attribute takes three values: off, polite, and assertive / rude. Generally speaking, polite is a good default: it won’t interrupt the user if they’re listening to their assistive tech tell them about other parts of the page and it’s supported by all major screen readers. Alternatively, the assertive and rude values will immediately interrupt the user with updates on the element’s content (note: there are some differences between which screen readers support which value). We’ll start by adding aria-live="polite" to our container:

+ +```html +
+ +
+``` + +

aria-busy

+

Once a container is aria-live, we can use aria-busy to tell assistive tech that the container is getting new content. When the container is not refreshing, aria-busy will be false, and when it is waiting for new content, it’ll be true. Let’s add that to our sample code:

+ +```html + +
+ +
+``` + +```html + +
+ +
+``` + +

Now our container is correctly signalling to screen readers when our container is “busy” getting new content! Additionally, it’ll automatically read the new content to users without requiring additional interaction from them.

+

Visual Loading Indicator: CSS Pseudo-Elements

+

Now that we’ve gotten our news container built to serve users relying on assistive tech, let’s add some styles so that sighted users will also know when our container is getting new content.

+

I’m not going to get into all the container’s layout styles: if you’re doing this in real life, you know your unique CSS concerns better than I do. I’ll just provide the bare minimum for a CSS-only loading indicator. We’re going to use a CSS Grid trick to make some layout overlapping simpler, but if your support requirements don’t match that, there are CSS position fallback solutions, too.

+

Container Layout

+

Let’s put some minimal layout CSS on our container. Note: I’ll be using nesting in these code samples, like you’d use in Sass or PostCSS with a nesting plugin.

+ +```scss +.news-wrapper { + /* 1. Grid Layout */ + display: grid; + grid-template: "content" 100% / auto; + + &::after { + /* 2. Grid Positioning */ + grid-area: content; + align-self: center; + justify-self: center; + + /* 3. Indicator Styles */ + content: ""; + margin: 3rem auto; + width: 4rem; + height: 4rem; + display: block; + border: .5rem solid blue; + border-left-color: transparent; + border-radius: 50%; + opacity: 0; + transition: opacity .1s; + pointer-events: none; + animation: loading-circle 1s ease-in-out infinite; + } +} + +.news { + /* 2. Grid Positioning */ + grid-area: content; +} + +@keyframes loading-circle { + to { + transform: rotate(360deg); + } +} +``` + +

Let’s take that CSS apart, section by section.

+

1. Grid Layout

+

This CSS makes our container a CSS Grid.

+ + +```css +.news-wrapper { + display: grid; + grid-template: "content" 100% / auto; +} +``` + +

This CSS grid-template declaration (shorthand for grid-template-rows, grid-template-columns, and grid-template-areas) creates one column (100% width) and one row (auto height: total height is set by content height), and it names that grid area content. “Why use a Grid if it’s only 1×1?” you may be asking. That’s a great question. Setting up 1×1 Grid allows us to position our contents without relying on position and related measurements. That brings us to internal positioning.

+

2. Grid Positioning

+

You may be used to position: relative (on a parent) and position: absolute (on a child) for centering a child in a parent when the parent contains normal content. However, using Grid lets us do that with fewer side effects.

+ +```css +.news { + grid-area: content; +} + +.news-wrapper::after { + grid-area: content; + align-self: center; + justify-self: center; +} +``` + +

We’ve positioned each child of the Grid container inside the content area. This will cause those elements (.news and .news-wrapper::after) to overlap. Even when there’s a .news article inside the container, both it and the ::after will be in content area, overlapping one another. Additionally, the ::after will be centered inside the container.

+

3. Indicator Styles

+

To summarize the visual styles, this loading indicator is an open, rotating circle. It’s a larger version of buffering spinners you might see in a media streaming site or app.

+

Notice that we’ve included pointer-events: none. This element is positioned above the .news (because it’s later in the DOM), which means even with opacity: 0, it’ll prevent users from clicking on parts of .news that are right behind it. Its purpose is visual decoration, so removing pointer-events prevents the spinner from “getting between” the user and the actual content. Note: we could also solve the overlap problem by changing z-index when it’s visible, but that requires adding a position property, and we’re keeping the CSS as simple as we can.

+

Styling with the Attributes

+

The last step is the selector for showing the spinner. When the content is busy reloading, we want to make .news less visible and make the spinner visible. We could use a class for that (you might use .is-reloading, if you use SMACSS state classes, for example). But we’ve already got a selector in the DOM and there’s no need to duplicate it with another class. Instead we can tie our last few lines of CSS directly to the aria-busy="live" attribute.

+ +```scss +.news-wrapper[aria-busy="true"] { + .news { + opacity: .2; + } + + &::after { + opacity: 1; + } +} +``` + +

Conclusion

+

In this project, we started by considering users who rely on assistive tech to access our site. As we continued, we discovered that by providing a good experience for them, we already had our selectors ready to go for providing visual cues to sighted users. If you’d like to see a demo of the whole thing put together, check out the CodePen below. And of course, if you turn on VoiceOver or another screen reader, you’ll see how both semantic and visual affordances are aligned for users.

+