Happier HTML5 Form Validation

HTML has given us loads of form validation stuff in the last few years. Dave Rupert puts a point on the UX problems with it:

If you’ve ever experimented with HTML5 Form Validation, you’ve probably been disappointed. The out-of-box experience isn’t quite what you want. Adding the required attribute to inputs works wonderfully. However the styling portion with input:invalid sorta sucks because empty inputs are trigger the :invalid state, even before the user has interacted with the page.

Fortunately, there is an invalid DOM event that does fire with useful timing: when the form is submitted. Remember this doesn’t buy you super deep browser support though. If you need that, look into polyfilling. I imagine the future of form validation is either HTML/CSS offering better and more controllable UX, or this.

Direct Link to Article — Permalink


Happier HTML5 Form Validation is a post from CSS-Tricks

Airplanes and Ashtrays

Harry Roberts wrote about design systems and how compromise has to be baked into them from the very start. He argues that we can’t be dictatorial about what is and isn’t permitted because design, whether that’s the design of a product, service or system, is always about compromise.

Harry writes:

Whenever you plan or design a system, you need to build in your own ashtrays—a codified way of dealing with the inevitability of somebody doing the wrong thing. Think of what your ideal scenario is—how do you want people to use whatever you’re building—and then try to identify any aspects of it which may be overly opinionated, prescriptive, or restrictive. Then try to preempt how people might try to avoid or circumvent these rules, and work back from there until you can design a safe middle-ground into your framework that can accept these deviations in the safest, least destructive way possible.

Direct Link to Article — Permalink


Airplanes and Ashtrays is a post from CSS-Tricks

Live Share / Teletype

Amanda Silver introduces “Visual Studio Live Share”, which:

enables developers using Visual Studio 2017 or Visual Studio Code to collaborate in real-time!

This goes a bit deeper than just a multiple-cursors thing. Both people get all the same fancy VS code UI stuff like IntelliSense and Peek.

GitHub’s Atom editor also has Teletype, which:

lets developers share their workspace with team members and collaborate on code in real time.

Atom has the concept of a host, in which:

As the host moves between files, collaborators follow along with the active tab automatically.

I’d be remiss not to mention CodePen has Collab Mode and Professor Mode, which require zero setup. Shoot someone a URL and go!

Direct Link to Article — Permalink


Live Share / Teletype is a post from CSS-Tricks

Recreating the Apple Watch Breathe App Animation

The Apple Watch comes with a stock app called Breathe that reminds you to, um, breathe. There’s actually more to it than that, but the thought of needing a reminder to breathe makes me giggle. The point is, the app has this kinda awesome interface with a nice animation.

Photo courtesy of Apple Support

I thought it would be fun to recreate the design in vanilla CSS. Here’s how far I got, which feels pretty close.

See the Pen Apple Watch Breathe App Animation by Geoff Graham (@geoffgraham) on CodePen.

Making the circles

First things first, we need a set of circles that make up that flower looking design. The app itself adds a circle to the layout for each minute that is added to the timer, but we’re going to stick with a static set of six for this demo. It feels like we could get tricky by using ::before and ::after to reduce the HTML markup, but we can keep it simple.

<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>

We’re going to make the full size of each circle 125px which is an arbitrary number. The important thing is that the default state of the circles should be all of them stacked on top of one another. We can use absolute positioning to do that.

.circle { border-radius: 50%; height: 125px; position: absolute; transform: translate(0, 0); width: 125px;
}

Note that we’re using the translate function of the transform property to center everything. I had originally tried using basic top, right, bottom, left properties but found later that animating translate is much smoother. I also originally thought that positioning the circles in the full expanded state would be the best place to start, but also found that the animations were cumbersome to create that way because it required resetting each one to center. Lessons learned!

If we were to stop here, there would be nothing on the screen and that’s because we have not set a background color. We’ll get to the nice fancy colors used in the app in a bit, but it might be helpful to add a white background for now with a hint of opacity to help see what’s happening as we work.

See the Pen Apple Watch Breathe App – Step 1 by Geoff Graham (@geoffgraham) on CodePen.

We need a container!

You may have noticed that our circles are nicely stacked, but nowhere near the actual center of the viewport. We’re going to need to wrap these bad boys in a parent element that we can use to position the entire bunch. Plus, that container will serve as the element that pulses and rotates the entire set later. That was another lesson I had to learn the hard way because I stubbornly did not want the extra markup of a container and thought I could work around it.

We’re calling the container .watch-face here and setting it to the same width and height as a single circle.

<div class="watch-face"> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div>
</div>

Now, we can add a little flex to the body element to center everything up.

body { background: #000; display: flex; align-items: center; justify-content: center; height: 100vh;
}

See the Pen Apple Watch Breathe App – Step 2 by Geoff Graham (@geoffgraham) on CodePen.

Next up, animate the circles

At this point, I was eager to see the circles positioned in that neat floral, overlapping arrangement. I knew that it would be difficult to animate the exact position of each circle without seeing them positioned first, so I overrode the transform property in each circle to see where they’d land.

We could set up a class for each circle, but using :nth-child seems easier.

.circle:nth-child(1) { transform: translate(-35px, -50px);
} /* Skipping 2-5 for brevity... */ .circle:nth-child(6) { transform: translate(35px, 50px);
}

It took me a few swings and misses to find coordinates that worked. It ultimately depends on the size of the circles and it may take some finessing.

See the Pen Apple Watch Breathe App – Step 3 by Geoff Graham (@geoffgraham) on CodePen.

Armed with the coordinates, we can register the animations. I removed the transform coordinates that were applied to each :nth-child and moved them into keyframes:

@keyframes circle-1 { 0% { transform: translate(0, 0); } 100% { transform: translate(-35px, -50px); }
} /* And so on... */

I have to admit that the way I went about it feels super clunky because each circle has it’s own animation. It would be slicker to have one animation that can rule them all to push and re-center the circles, but maybe someone else reading has an idea and can share it in the comments.

Now we can apply those animations to each :nth-child in place of transform:

.circle:nth-child(1) { animation: circle-1 4s ease alternate infinite;
} /* And so on... */

Note that we set the animation-timing-function to ease because that feels smooth…at least to me! We also set the animation-direction to alternate so it plays back and forth and set the animation-iteration-count to inifinite so it stays running.

See the Pen Apple Watch Breathe App – Step 4 by Geoff Graham (@geoffgraham) on CodePen.

Color, color, color!

Oh yeah, let’s paint this in! From what I can tell, there are really only two colors in the design and the opacity is what makes it feel like more of a spectrum.

The circles on the left are a greenish color and the ones on the right are sorta blue. We can select the odd-numbered circles to apply the green and the even-numbered ones to apply the blue.

.circle:nth-child(odd) { background: #61bea2;
} .circle:nth-child(even) { background: #529ca0;
}

Oh, and don’t forget to remove the white background from the .circle element. It won’t hurt anything, but it’s nice to clean up after ourselves. I admittedly forgot to do this on the first go.

See the Pen Apple Watch Breathe App – Step 5 by Geoff Graham (@geoffgraham) on CodePen.

Pulse and rotate

Remember that pesky .watch-face container we created? Well, we can animate it to pulse the circles in and out while rotating the entire bunch.

I had totally forgotten that transform functions can be chained together. That makes things a little cleaner because it allows us to apply scale() and rotate() on the same line.

@keyframes pulse { 0% { transform: scale(.15) rotate(180deg); } 100% { transform: scale(1); }
}

…and apply that to the .watch-face element.

.watch-face { height: 125px; width: 125px; animation: pulse 4s cubic-bezier(0.5, 0, 0.5, 1) alternate infinite;
}

Like the circles, we want the animation to run both ways and repeat infinitely. In this case, the scale drops to a super small size as the circles stack on top of each other and the whole thing rotates halfway on the way out before returning back on the way in.

I’ll admit that I am not a buff when it comes to finding the right animation-timing-function for the smoothest or exact animations. I played with cubic-bezier and found something I think feels pretty good, but it’s possible that a stock value like ease-in would work just as well.

All together now!

Here’s everything smushed into the same demo.

See the Pen Apple Watch Breathe App Animation by Geoff Graham (@geoffgraham) on CodePen.

Now, just remember to breathe and let’s all have a peaceful day. ☮️


Recreating the Apple Watch Breathe App Animation is a post from CSS-Tricks

Simple Patterns for Separation (Better Than Color Alone)

Color is pretty good for separating things. That’s what your basic pie chart is, isn’t it? You tell the slices apart by color. With enough color contrast, you might be OK, but you might be even better off (particularly where accessibility is concerned) using patterns, or a combination.

Patrick Dillon tackled the Pie Chart thing

Enhancing Charts With SVG Patterns:

When one of the slices is filled with something more than color, it’s easier to figure out [who the Independents are]:

See the Pen Political Party Affiliation – #2 by Patrick Dillon (@pdillon) on CodePen.

Filling a pie slice with a pattern is not a common charting library feature (yet), but if your library of choice is SVG-based, you are free to implement SVG patterns.

As in, literally a <pattern /> in SVG!

Here’s a simple one for horizontal lines:

<pattern id="horzLines" width="8" height="4" patternUnits="userSpaceOnUse"> <line x1="0" y1="0" x2="8" y2="0" style="stroke:#999;stroke-width:1.5" />
</pattern>

Now any SVG element can use that pattern as a fill. Even strokes. Here’s an example of mixed usage of two simple patterns:

See the Pen Simple Line Patterns by Chris Coyier (@chriscoyier) on CodePen.

That’s nice for filling SVG elements, but what about HTML elements?

Irene Ros created Pattern Fills that are SVG based, but usable in CSS also.

Using SVG Patterns as Fills:

There are several ways to use Pattern Fills:

  • You can use the patterns.css file that contains all the current patterns. That will only work for non-SVG elements.

  • You can use individual patterns, but copying them from the sample pages. CSS class definitions can be found here and SVG pattern defs can be found here

  • You can add your own patterns or modify mine! The conversion process from SVG document to pattern is very tedious. The purpose of the pattern fills toolchain is to simplify this process. You can clone the repo, run npm install and grunt dev to get a local server going. After that, any changes or additions to the src/patterns/**/* files will be automatically picked up and will re-render the CSS file and the sample pages. If you make new patterns, send them over in a pull request!

Here’s me applying them to SVG elements (but could just as easily be applied to HTML elements):

See the Pen Practical Patterns by Chris Coyier (@chriscoyier) on CodePen.

The CSS usage is as base64 data URLs though, so once they are there they aren’t super duper manageable/changeable.

Here’s Irene with an old timey chart, using d3:

Managing an SVG pattern in CSS

If your URL encode the SVG just right, you and plop it right into CSS and have it remain fairly managable.

See the Pen Simple Line Patterns by Chris Coyier (@chriscoyier) on CodePen.

Other Examples Combining Color

Here’s one by John Schulz:

See the Pen SVG Colored Patterns by Chris Coyier (@chriscoyier) on CodePen.

Ricardo Marimón has an example creating the pattern in d3. The pattern looks largely the same on the slices, but perhaps it’s a start to modify.

Other Pattern Sources

We rounded a bunch of them up recently!


Simple Patterns for Separation (Better Than Color Alone) is a post from CSS-Tricks

How to Disable Links

The topic of disabling links popped up at my work the other day. Somehow, a “disabled” anchor style was added to our typography styles last year when I wasn’t looking. There is a problem though: there is no real way to disable an <a> link (with a valid href attribute) in HTML. Not to mention, why would you even want to? Links are the basis of the web.

At a certain point, it looked like my co-workers were not going to accept this fact, so I started thinking of how this could be accomplished. Knowing that it would take a lot, I wanted to prove that it was not worth the effort and code to support such an unconventional interaction, but I feared that by showing it could be done they would ignore all my warnings and just use my example as proof that it was OK. This hasn’t quite shaken out for me yet, but I figured we could go through my research.

First, things first:

Just don’t do it.

A disabled link is not a link, it’s just text. You need to rethink your design if it calls for disabling a link.

Bootstrap has examples of applying the .disabled class to anchor tags, and I hate them for it. At least they mention that the class only provides a disabled style, but this is misleading. You need to do more than just make a link look disabled if you really want to disable it.

Surefire way: remove the href

If you have decided that you are going to ignore my warning and proceed with disabling a link, then removing the href attribute is the best way I know how.

Straight from the official Hyperlink spec:

The href attribute on a and area elements is not required; when those elements do not have href attributes they do not create hyperlinks.

An easier to understand definition from MDN:

This attribute may be omitted (as of HTML5) to create a placeholder link. A placeholder link resembles a traditional hyperlink, but does not lead anywhere.

Here is basic JavaScript code to set and remove the href attribute:

/* * Use your preferred method of targeting a link * * document.getElementById('MyLink'); * document.querySelector('.link-class'); * document.querySelector('[href="https://unfetteredthoughts.net"]'); */
// "Disable" link by removing the href property
link.href = '';
// Enable link by setting the href property
link.href = 'https://unfetteredthoughts.net';

Styling this via CSS is also pretty straightforward:

a { /* Disabled link styles */
}
a:link, a:visited { /* or a[href] */ /* Enabled link styles */
}

That’s all you need to do!

That’s not enough, I want something more complex so that I can look smarter!

If you just absolutely have to over-engineer some extreme solution, here are some things to consider. Hopefully, you will take heed and recognize that what I am about to show you is not worth the effort.

First, we need to style our link so that it looks disabled.

.isDisabled { color: currentColor; cursor: not-allowed; opacity: 0.5; text-decoration: none;
}
<a class="isDisabled" href="https://unfetteredthoughts.net">Disabled Link</a>

Setting color to currentColor should reset the font color back to your normal, non-link text color. I am also setting the mouse cursor to not-allowed to display a nice indicator on hover that the normal action is not allowed. Already, we have left out non-mouse users that can’t hover, mainly touch and keyboard, so they won’t get this indication. Next the opacity is cut to half. According to WCAG, disabled elements do not need to meet color contrast guidelines. I think this is very risky since it’s basically plain text at this point, and dropping the opacity in half would make it very hard to read for users with low-vision, another reason I hate this. Lastly, the text decoration underline is removed as this is usually the best indicator something is a link. Now this looks like a disabled link!

But it’s not really disabled! A user can still click/tap on this link. I hear you screaming about pointer-events.

.isDisabled { ... pointer-events: none;
}

Ok, we are done! Disabled link accomplished! Except, it’s only really disabled for mouse users clicking and touch users tapping. What about browsers that don’t support pointer-events? According to caniuse, this is not supported for Opera Mini and IE<11. IE11 and Edge actually don't support pointer-events unless display is set to block or inline-block. Also, setting pointer-events to none overwrites our nice not-allowed cursor, so now mouse users will not get that additional visual indication that the link is disabled. This is already starting to fall apart. Now we have to change our markup and CSS…

.isDisabled { cursor: not-allowed; opacity: 0.5;
}
.isDisabled > a { color: currentColor; display: inline-block; /* For IE11/ MS Edge bug */ pointer-events: none; text-decoration: none;
}
<span class="isDisabled"><a href="https://unfetteredthoughts.net">Disabled Link</a></span>

Wrapping the link in a <span> and adding the isDisabled class gives us half of our disabled visual style. A nice side-affect here is that the disabled class is now generic and can be used on other elements, like buttons and form elements. The actual anchor tag now has the pointer-events and text-decoration set to none.

What about keyboard users? Keyboard users will use the ENTER key to activate links. pointer-events are only for pointers, there is no keyboard-events. We also need to prevent activation for older browsers that don’t support pointer-events. Now we have to introduce some JavaScript.

Bring in the JavaScript

// After using preferred method to target link
link.addEventListener('click', function (event) { if (this.parentElement.classList.contains('isDisabled')) { event.preventDefault(); }
});

Now our link looks disabled and does not respond to activation via clicks, taps, and the ENTER key. But we are still not done! Screen reader users have no way of knowing that this link is disabled. We need to describe this link as being disabled. The disabled attribute is not valid on links, but we can use aria-disabled="true".

<span class="isDisabled"><a href="https://unfetteredthoughts.net" aria-disabled="true">Disabled Link</a></span>

Now I am going to take this opportunity to style the link based on the aria-disabled attribute. I like using ARIA attributes as hooks for CSS because having improperly styled elements is an indicator that important accessibility is missing.

.isDisabled { cursor: not-allowed; opacity: 0.5;
}
a[aria-disabled="true"] { color: currentColor; display: inline-block; /* For IE11/ MS Edge bug */ pointer-events: none; text-decoration: none;
}

Now our links look disabled, act disabled, and are described as disabled.

Unfortunately, even though the link is described as disabled, some screen readers (JAWS) will still announce this as clickable. It does that for any element that has a click listener. This is because of developer tendency to make non-interactive elements like div and span as pseudo-interactive elements with a simple listener. Nothing we can do about that here. Everything we have done to remove any indication that this is a link is foiled by the assistive technology we were trying to fool, ironically because we have tried to fool it before.

But what if we moved the listener to the body?

document.body.addEventListener('click', function (event) { // filter out clicks on any other elements if (event.target.nodeName == 'A' && event.target.getAttribute('aria-disabled') == 'true') { event.preventDefault(); }
});

Are we done? Well, not really. At some point we will need to enable these links so we need to add additional code that will toggle this state/behavior.

function disableLink(link) {
// 1. Add isDisabled class to parent span link.parentElement.classList.add('isDisabled');
// 2. Store href so we can add it later link.setAttribute('data-href', link.href);
// 3. Remove href link.href = '';
// 4. Set aria-disabled to 'true' link.setAttribute('aria-disabled', 'true');
}
function enableLink(link) {
// 1. Remove 'isDisabled' class from parent span link.parentElement.classList.remove('isDisabled');
// 2. Set href link.href = link.getAttribute('data-href');
// 3. Remove 'aria-disabled', better than setting to false link.removeAttribute('aria-disabled');
}

That’s it. We now have a disabled link that is visually, functionally, and semantically disabled for all users. It only took 10 lines of CSS, 15 lines of JavaScript (including 1 listener on the body), and 2 HTML elements.

Seriously folks, just don’t do it.


How to Disable Links is a post from CSS-Tricks

4 Reasons to Go PRO on CodePen

I could probably list about 100 reasons, since as a founder, user, and (ahem) PRO member of CodePen myself, I’m motivated to do so. But let me just list a few here. Some of these are my favorites, some are what PRO members have told us are their favorite, and some are lesser-known but very awesome.

1) No-hassle Debug View

Debug View is a way to look at your the Pen you’ve built with zero CodePen UI around it and no <iframe> containing it. Raw output! That’s a dangerous thing, in the world of user-generated code. It could be highly abused if we let it go unchecked. The way it works is fairly simple.

You can use Debug View if :

  1. You’re logged in
  2. The Pen is PRO-owned

Logging in isn’t too big of a deal, but sometimes that’s a pain if you just wanna shoot over a Debug View URL to your phone or to CrossBrowserTesting or something. If you’re PRO, you don’t worry about it at all, Debug View is totally unlocked for your Pens.

2) Collab with anybody anytime

Collab Mode on CodePen is the one that’s like Google Docs: people can work together in real time on the same Pen. You type, they see you type, they type, you see what they type.

Here’s what is special about that:

  • There is nothing to install or set up, just shoot anybody a link.
  • Only you need to be PRO. Nobody else does. They don’t even have to be logged in.

Basically, you can be coding together with someone (or just one or the other of you watching, if you please) in about 2 seconds. Here’s a short silent video if you wanna see:

People use it for all sorts of things. In classrooms and educational contexts, for hiring interviews, and just for working out problems with people across the globe.

Drag and drop uploading

Need to quickly host an asset (like an image) somewhere? As in, quickly get a URL for it that you can use and count on forever? It’s as easy as can be on CodePen.

If you’re working on a Project, you can drag and drop files right into the sidebar as well:

Save Anything Privately

Privacy is unlimited on CodePen. If you’re PRO, you can make unlimited Private Pens and Private Collections. You’re only limited in Private Projects by the total number of Projects on your plan. This is the number one PRO feature on CodePen for a variety of reasons. People use them to keep client work safe. People use them to experiment without anyone seeing messy tidbits. People use them to keep in-progress ideas.


I didn’t even mention my actual favorite CodePen PRO feature. I’ll have to share that one some other time 😉

Go PRO on CodePen


4 Reasons to Go PRO on CodePen is a post from CSS-Tricks

SVG as a Placeholder

It wasn’t long ago when Mikael Ainalem’s Pen demonstrated how you might use SVG outlines in HTML then lazyload the image (later turned into a webpack loader by Emil Tholin). It’s kind of like a skeleton screen, in that it gives the user a hint of what’s coming. Or the blur up technique, which loads a very small image blurrily blown up as the placeholder image.

José M. Pérez documents those, plus some more basic options (nothing, an image placeholder, or a solid color), and best of all, a very clever new idea using Primitive (of which there is a mac app and JavaScript version), which creates overlapping SVG shapes to use as the placeholder image. Probably a bit bigger-in-size than some of the other techniques, but still much smaller than a high res JPG!

Direct Link to Article — Permalink


SVG as a Placeholder is a post from CSS-Tricks

Accessible Web Apps with React, TypeScript, and AllyJS

Accessibility is an aspect of web development that is often overlooked. I would argue that it is as vital as overall performance and code reusability. We justify our endless pursuit of better performance and responsive design by citing the users, but ultimately these pursuits are done with the user’s device in mind, not the user themselves and their potential disabilities or restrictions.

A responsive app should be one that delivers its content based on the needs of the user, not only their device.

Luckily, there are tools to help alleviate the learning curve of accessibility-minded development. For example, GitHub recently released their accessibility error scanner, AccessibilityJS and Deque has aXe. This article will focus on a different one: Ally.js, a library simplifying certain accessibility features, functions, and behaviors.


One of the most common pain points regarding accessibility is dialog windows.

There’re a lot of considerations to take in terms of communicating to the user about the dialog itself, ensuring ease of access to its content, and returning to the dialog’s trigger upon close.

A demo on the Ally.js website addresses this challenge which helped me port its logic to my current project which uses React and TypeScript. This post will walk through building an accessible dialog component.

Demo of accessible dialog window using Ally.js within React and TypeScript

View the live demo

Project Setup with create-react-app

Before getting into the use of Ally.js, let’s take a look at the initial setup of the project. The project can be cloned from GitHub or you can follow along manually. The project was initiated using create-react-app in the terminal with the following options:

create-react-app my-app --scripts-version=react-scripts-ts

This created a project using React and ReactDOM version 15.6.1 along with their corresponding @types.

With the project created, let’s go ahead and take a look at the package file and project scaffolding I am using for this demo.

Project architecture and package.json file

As you can see in the image above, there are several additional packages installed but for this post we will ignore those related to testing and focus on the primary two, ally.js and babel-polyfill.

Let’s install both of these packages via our terminal.

yarn add ally.js --dev && yarn add babel-polyfill --dev

For now, let’s leave `/src/index.tsx` alone and hop straight into our App container.

App Container

The App container will handle our state that we use to toggle the dialog window. Now, this could also be handled by Redux but that will be excluded in lieu of brevity.

Let’s first define the state and toggle method.

interface AppState { showDialog: boolean;
} class App extends React.Component<{}, AppState> { state: AppState; constructor(props: {}) { super(props); this.state = { showDialog: false }; } toggleDialog() { this.setState({ showDialog: !this.state.showDialog }); }
}

The above gets us started with our state and the method we will use to toggle the dialog. Next would be to create an outline for our render method.

class App extends React.Component<{}, AppState> { ... render() { return ( <div className="site-container"> <header> <h1>Ally.js with React &amp; Typescript</h1> </header> <main className="content-container"> <div className="field-container"> <label htmlFor="name-field">Name:</label> <input type="text" id="name-field" placeholder="Enter your name" /> </div> <div className="field-container"> <label htmlFor="food-field">Favourite Food:</label> <input type="text" id="food-field" placeholder="Enter your favourite food" /> </div> <div className="field-container"> <button className='btn primary' tabIndex={0} title='Open Dialog' onClick={() => this.toggleDialog()} > Open Dialog </button> </div> </main> </div> ); }
}

Don’t worry much about the styles and class names at this point. These elements can be styled as you see fit. However, feel free to clone the GitHub repo for the full styles.

At this point we should have a basic form on our page with a button that when clicked toggles our showDialog state value. This can be confirmed by using React’s Developer Tools.

So let’s now have the dialog window toggle as well with the button. For this let’s create a new Dialog component.

Dialog Component

Let’s look at the structure of our Dialog component which will act as a wrapper of whatever content (children) we pass into it.

interface Props { children: object; title: string; description: string; close(): void;
} class Dialog extends React.Component<Props> { dialog: HTMLElement | null; render() { return ( <div role="dialog" tabIndex={0} className="popup-outer-container" aria-hidden={false} aria-labelledby="dialog-title" aria-describedby="dialog-description" ref={(popup) => { this.dialog = popup; } } > <h5 id="dialog-title" className="is-visually-hidden" > {this.props.title} </h5> <p id="dialog-description" className="is-visually-hidden" > {this.props.description} </p> <div className="popup-inner-container"> <button className="close-icon" title="Close Dialog" onClick={() => { this.props.close(); }} > × </button> {this.props.children} </div> </div> ); }
}

We begin this component by creating the Props interface. This will allow us to pass in the dialog’s title and description, two important pieces for accessibility. We will also pass in a close method, which will refer back to the toggleDialog method from the App container. Lastly, we create the functional ref to the newly created dialog window to be used later.

The following styles can be applied to create the dialog window appearance.

.popup-outer-container { align-items: center; background: rgba(0, 0, 0, 0.2); display: flex; height: 100vh; justify-content: center; padding: 10px; position: absolute; width: 100%; z-index: 10;
} .popup-inner-container { background: #fff; border-radius: 4px; box-shadow: 0px 0px 10px 3px rgba(119, 119, 119, 0.35); max-width: 750px; padding: 10px; position: relative; width: 100%;
} .popup-inner-container:focus-within { outline: -webkit-focus-ring-color auto 2px;
} .close-icon { background: transparent; color: #6e6e6e; cursor: pointer; font: 2rem/1 sans-serif; position: absolute; right: 20px; top: 1rem;
}

Now, let’s tie this together with the App container and then get into Ally.js to make this dialog window more accessible.

App Container

Back in the App container, let’s add a check inside of the render method so any time the showDialog state updates, the Dialog component is toggled.

class App extends React.Component<{}, AppState> { ... checkForDialog() { if (this.state.showDialog) { return this.getDialog(); } else { return false; } } getDialog() { return ( <Dialog title="Favourite Holiday Dialog" description="Add your favourite holiday to the list" close={() => { this.toggleDialog(); }} > <form className="dialog-content"> <header> <h1 id="dialog-title">Holiday Entry</h1> <p id="dialog-description">Please enter your favourite holiday.</p> </header> <section> <div className="field-container"> <label htmlFor="within-dialog">Favourite Holiday</label> <input id="within-dialog" /> </div> </section> <footer> <div className="btns-container"> <Button type="primary" clickHandler={() => { this.toggleDialog(); }} msg="Save" /> </div> </footer> </form> </Dialog> ); } render() { return ( <div className="site-container"> {this.checkForDialog()} ... ); }
}

What we’ve done here is add the methods checkForDialog and getDialog.

Inside of the render method, which runs any time the state updates, there is a call to run checkForDialog. So upon clicking the button, the showDialog state will update, causing a re-render, calling checkForDialog again. Only now, showDialog is true, triggering getDialog. This method returns the Dialog component we just built to be rendered onto the screen.

The above sample includes a Button component that has not been shown.

Now, we should have the ability to open and close our dialog. So let’s take a look at what problems exist in terms of accessibility and how we can address them using Ally.js.


Using only your keyboard, open the dialog window and try to enter text into the form. You’ll notice that you must tab through the entire document to reach the elements within the dialog. This is a less-than-ideal experience. When the dialog opens, our focus should be the dialog  –  not the content behind it. So let’s look at our first use of Ally.js to begin remedying this issue.

Ally.js

Ally.js is a library providing various modules to help simplify common accessibility challenges. We will be using four of these modules for the Dialog component.

The .popup-outer-container acts as a mask that lays over the page blocking interaction from the mouse. However, elements behind this mask are still accessible via keyboard, which should be disallowed. To do this the first Ally module we’ll incorporate is maintain/disabled. This is used to disable any set of elements from being focussed via keyboard, essentially making them inert.

Unfortunately, implementing Ally.js into a project with TypeScript isn’t as straightforward as other libraries. This is due to Ally.js not providing a dedicated set of TypeScript definitions. But no worries, as we can declare our own modules via TypeScript’s types files.

In the original screenshot showing the scaffolding of the project, we see a directory called types. Let’s create that and inside create a file called `global.d.ts`.

Inside of this file let’s declare our first Ally.js module from the esm/ directory which provides ES6 modules but with the contents of each compiled to ES5. These are recommended when using build tools.

declare module 'ally.js/esm/maintain/disabled';

With this module now declared in our global types file, let’s head back into the Dialog component to begin implementing the functionality.

Dialog Component

We will be adding all the accessibility functionality for the Dialog to its component to keep it self-contained. Let’s first import our newly declared module at the top of the file.

import Disabled from 'ally.js/esm/maintain/disabled';

The goal of using this module will be once the Dialog component mounts, everything on the page will be disabled while filtering out the dialog itself.

So let’s use the componentDidMount lifecycle hook for attaching any Ally.js functionality.

interface Handle { disengage(): void;
} class Dialog extends React.Component<Props, {}> { dialog: HTMLElement | null; disabledHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); } componentWillUnmount() { this.disabledHandle.disengage(); } ...
}

When the component mounts, we store the Disabled functionality to the newly created component property disableHandle. Because there are no defined types yet for Ally.js we can create a generic Handle interface containing the disengage function property. We will be using this Handle again for other Ally modules, hence keeping it generic.

By using the filter property of the Disabled import, we’re able to tell Ally.js to disable everything in the document except for our dialog reference.

Lastly, whenever the component unmounts we want to remove this behaviour. So inside of the componentWillUnmount hook, we disengage() the disableHandle.


We will now follow this same process for the final steps of improving the Dialog component. We will use the additional Ally modules:

  • maintain/tab-focus
  • query/first-tabbable
  • when/key

Let’s update the `global.d.ts` file so it declares these additional modules.

declare module 'ally.js/esm/maintain/disabled';
declare module 'ally.js/esm/maintain/tab-focus';
declare module 'ally.js/esm/query/first-tabbable';
declare module 'ally.js/esm/when/key';

As well as import them all into the Dialog component.

import Disabled from 'ally.js/esm/maintain/disabled';
import TabFocus from 'ally.js/esm/maintain/tab-focus';
import FirstTab from 'ally.js/esm/query/first-tabbable';
import Key from 'ally.js/esm/when/key';

Tab Focus

After disabling the document with the exception of our dialog, we now need to restrict tabbing access further. Currently, upon tabbing to the last element in the dialog, pressing tab again will begin moving focus to the browser’s UI (such as the address bar). Instead, we want to leverage tab-focus to ensure the tab key will reset to the beginning of the dialog, not jump to the window.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); } componentWillUnmount() { this.disabledHandle.disengage(); this.focusHandle.disengage(); } ...
}

We follow the same process here as we did for the disabled module. Let’s create a focusHandle property which will assume the value of the TabFocus module import. We define the context to be the active dialog reference on mount and then disengage() this behaviour, again, when the component unmounts.

At this point, with a dialog window open, hitting tab should cycle through the elements within the dialog itself.

Now, wouldn’t it be nice if the first element of our dialog was already focused upon opening?

First Tab Focus

Leveraging the first-tabbable module, we are able to set focus to the first element of the dialog window whenever it mounts.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); let element = FirstTab({ context: this.dialog, defaultToContext: true, }); element.focus(); } ...
}

Within the componentDidMount hook, we create the element variable and assign it to our FirstTab import. This will return the first tabbable element within the context that we provide. Once that element is returned, calling element.focus() will apply focus automatically.

Now, that we have the behavior within the dialog working pretty well, we want to improve keyboard accessibility. As a strict laptop user myself (no external mouse, monitor, or any peripherals) I tend to instinctively press esc whenever I want to close any dialog or popup. Normally, I would write my own event listener to handle this behavior but Ally.js provides the when/key module to simplify this process as well.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; keyHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); let element = FirstTab({ context: this.dialog, defaultToContext: true, }); element.focus(); this.keyHandle = Key({ escape: () => { this.props.close(); }, }); } componentWillUnmount() { this.disabledHandle.disengage(); this.focusHandle.disengage(); this.keyHandle.disengage(); } ...
}

Again, we provide a Handle property to our class which will allow us to easily bind the esc functionality on mount and then disengage() it on unmount. And like that, we’re now able to easily close our dialog via the keyboard without necessarily having to tab to a specific close button.

Lastly (whew!), upon closing the dialog window, the user’s focus should return to the element that triggered it. In this case, the Show Dialog button in the App container. This isn’t built into Ally.js but a recommended best practice that, as you’ll see, can be added in with little hassle.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; keyHandle: Handle; focusedElementBeforeDialogOpened: HTMLInputElement | HTMLButtonElement; componentDidMount() { if (document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLButtonElement) { this.focusedElementBeforeDialogOpened = document.activeElement; } this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); let element = FirstTab({ context: this.dialog, defaultToContext: true, }); this.keyHandle = Key({ escape: () => { this.props.close(); }, }); element.focus(); } componentWillUnmount() { this.disabledHandle.disengage(); this.focusHandle.disengage(); this.keyHandle.disengage(); this.focusedElementBeforeDialogOpened.focus(); } ...
}

What has been done here is a property, focusedElementBeforeDialogOpened, has been added to our class. Whenever the component mounts, we store the current activeElement within the document to this property.

It’s important to do this before we disable the entire document or else document.activeElement will return null.

Then, like we had done with setting focus to the first element in the dialog, we will use the .focus() method of our stored element on componentWillUnmount to apply focus to the original button upon closing the dialog. This functionality has been wrapped in a type guard to ensure the element supports the focus() method.


Now, that our Dialog component is working, accessible, and self-contained we are ready to build our App. Except, running yarn test or yarn build will result in an error. Something to this effect:

[path]/node_modules/ally.js/esm/maintain/disabled.js:21 import nodeArray from '../util/node-array'; ^^^^^^ SyntaxError: Unexpected token import

Despite Create React App and its test runner, Jest, supporting ES6 modules, an issue is still caused with the ESM declared modules. So this brings us to our final step of integrating Ally.js with React, and that is the babel-polyfill package.

All the way in the beginning of this post (literally, ages ago!), I showed additional packages to install, the second of which being babel-polyfill. With this installed, let’s head to our app’s entry point, in this case ./src/index.tsx.

Index.tsx

At the very top of this file, let’s import babel-polyfill. This will emulate a full ES2015+ environment and is intended to be used in an application rather than a library/tool.

import 'babel-polyfill';

With that, we can return to our terminal to run the test and build scripts from create-react-app without any error.

Demo of accessible dialog window using Ally.js within React and TypeScript

View the live demo


Now that Ally.js is incorporated into your React and TypeScript project, more steps can be taken to ensure your content can be consumed by all users, not just all of their devices.

For more information on accessibility and other great resources please visit these resources:

  • Accessible Web Apps with React, TypeScript & Ally.js on Github
  • Start Building Accessible Web Applications Today
  • HTML Codesniffer
  • Web Accessibility Best Practices
  • Writing CSS with Accessibility in Mind
  • Accessibility Checklist

Accessible Web Apps with React, TypeScript, and AllyJS is a post from CSS-Tricks

Aspect Ratios for Grid Items

We’ve covered Aspect Ratio Boxes before. It involves trickery with padding such that an element’s width and height are in proportion to your liking. It’s not an ultra-common need, since fixing an element’s height is asking for trouble, but it comes up.

One way to lower the risk is The Psuedo Element Tactic, in which a pseudo element pushes its parent element to the aspect ratio, but if the content inside pushes it taller, it will get taller, aspect ratio be damned.

You can use that technique in CSS grid with grid items! Although there are a couple of different ways to apply it that are worth thinking about.

Remember that grid areas and the element that occupy them aren’t necessarily the same size.

We just covered this. That article started as a section in this article but felt important enough to break off into its own concept.

Knowing this, it leads to: do you need the grid area to have an aspect ratio, and the element will stretch within? Or does the element just need an aspect ratio regardless of the grid area it is in?

Scenario 1) Just the element inside needs to have an aspect ratio.

Cool. This is arguably easier. Make sure the element is 100% as wide as the grid area, then apply a pseudo element to handle the height-stretching aspect ratio.

<div class="grid"> <div style="--aspect-ratio: 2/1;">2/1</div> <div style="--aspect-ratio: 3/1;">3/1</div> <div style="--aspect-ratio: 1/1;">1/1</div>
</div>
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; place-items: start;
}
.grid > * { background: orange; width: 100%;
}
.grid > [style^='--aspect-ratio']::before { content: ""; display: inline-block; width: 1px; height: 0; padding-bottom: calc(100% / (var(--aspect-ratio)));
}

Which leads to this:

Note that you don’t need to apply aspect ratios through custom properties necessarily. You can see where the padding-bottom is doing the heavy lifting and that value could be hard-coded or whatever else.

Scenario 2) Span as many columns as needed for width

I bet it’s more likely that what you are needing is a way to make a, say 2-to-1 aspect ratio element, to actually span two columns, not be trapped in one. Doing this is a lot like what we just did above, but adding in rules to do that column spanning.

[style="--aspect-ratio: 1/1;"] { grid-column: span 1;
}
[style="--aspect-ratio: 2/1;"] { grid-column: span 2;
}
[style="--aspect-ratio: 3/1;"] { grid-column: span 3;
}

If we toss grid-auto-flow: dense; in there too, we can get items with a variety of aspect ratios packing in nicely as they see fit.

Now is a good time to mention there little ways to screw up exact aspect ratios here. The line-height on some text might push a box taller than you want. If you want to use grid-gap, that might throw ratios out of whack. If you need to get super exact with the ratios, you might have more luck hard-coding values.

Doing column spanning also gets tricky if you’re in a grid that doesn’t have a set number of rows. Perhaps you’re doing a repeat/auto-fill thing. You might end up in a scenario with unequal columns that won’t be friendly to aspect ratios. Perhaps we can dive into that another time.

Scenario 3) Force stuff

Grid has a two-dimensional layout capability and, if we like, we could get our aspect ratios just by forcing the grid areas to the height and width we want. For instance, nothing is stopping you from hard-coding heights and widths into columns and rows:

.grid { display: grid; grid-template-columns: 200px 100px 100px; grid-template-rows: 100px 200px 300px;
}

We normally don’t think that way, because we want elements to be flexible and fluid, hence the percentage-based techniques used above for aspect ratios. Still, it’s a thing you can do.

See the Pen Aspect Ratio Boxes Filling by Chris Coyier (@chriscoyier) on CodePen.

This example forces grid areas to be the size they are and the elements stretch to fill, but you could fix the element sizes as well.

Real world example

Ben Goshow wrote to me trying to pull this off, which is what spurred this:

Part of the issue there was not only getting the boxes to have aspect ratios, but then having alignment ability inside. There are a couple of ways to approach that, but I’d argue the easiest way is nested grids. Make the grid element display: grid; and use the alignment capabilities of that new internal grid.

See the Pen CSS Grid Items with Aspect Ratio and Internal Alignment by Chris Coyier (@chriscoyier) on CodePen.

Note in this demo instead of spanning rows, the elements are explicitly placed (not required, an example alternative layout method).


Aspect Ratios for Grid Items is a post from CSS-Tricks