Front End Web Development

Short note on what CSS display properties do to table semantics

Css Tricks - Wed, 03/14/2018 - 10:14am

We've blogged about responsive tables a number of times over the years. There's a variety of techniques, and which you choose should be based on the data in the table and the UX you're going for. But many of them rely upon resetting a table element's natural display value to something else, for example display: block. Steve Faulkner warns us:

When CSS display: block or display: grid or display: flex is set on the table element, bad things happen. The table is no longer represented as a table in the accessibility tree, row elements/semantics are no longer represented in any form.

He argues that the browser is making a mistake here by altering those semantics, but since they do, it's good to know it's fixable with (a slew of) ARIA roles.

Here's more from Adrian Roselli including a demo with proper markup.

Direct Link to ArticlePermalink

The post Short note on what CSS display properties do to table semantics appeared first on CSS-Tricks.

A Browser-Based, Open Source Tool for Alternative Communication

Css Tricks - Wed, 03/14/2018 - 5:15am

Shay Cojocaru contributed to this post.

Have you ever lost your voice? How did you handle that? Perhaps you carried a notebook and pen to scribble notes. Or jotted quick texts on your phone.

Have you ever traveled somewhere that you didn't speak or understand the language everyone around you was speaking? How did you order food, or buy a train ticket? Perhaps you used a translation phrasebook, or Google translate. Perhaps you relied mostly on physical gestures.

All of these solutions are examples of communication methods — tools and strategies — that you may have used before to solve everyday communicative challenges. The preceding examples are temporary solutions to temporary challenges. Your laryngitis cleared up. You returned home, where accomplishing daily tasks in your native tongue is almost effortless. Now imagine that these situational obstacles were somehow permanent.

I grew up knowing the challenges and creativity needed for effective communication when verbal speech is impeded. My younger sister speaks one word: “mama.” When we were little, I remember our mom laying a white sheet over a chair to take pictures of everyday items — an apple, a fork, a toothbrush. She painstakingly printed, cut out, laminated, and organized these images for my sister to use to point at what she wanted to say. We carried her words in plastic baggies.

As we both grew up, and technology evolved, her communication options expanded exponentially. From laminated paper, to a proprietary touchscreen device with text-to-speech functionality, to a communication app on the iTouch, and later the iPad.

Different people experience difficulty verbalizing speech for a multitude of reasons. As in my sister’s case, sometimes it’s genetic. Sometimes it’s situational. The reasons may be temporary, chronic, or permanent. Augmentative and alternative communication (AAC) is an umbrella term encompassing various communication methods used to supplement or replace speech. The United States Society for Augmentative and Alternative Communication (USAAC) defines AAC-devices as including “all forms of communication (other than oral speech) that are used to express thoughts, needs, wants, and ideas.” The fact that you’re reading these words is an example of AAC — writing is a mechanism that augments my verbal communication.

The range of communication solutions people employ are as varied as the reasons they are needed. Examples range from printed picture grids, to text-to-speech apps, to switches which enable typing using morse code, to software that tracks eye movement or detects facial movements. (The software behind Stephen Hawking’s AAC is open source!)

The Convention on the Rights of Persons with Disabilities (CRPD), an international human rights treaty intended to protect the rights and dignity of people with disabilities, includes accessibility to communication and information. Huge challenges exist in making this access universal. Current solutions can be prohibitively expensive. According to the World Health Organization, in many low-income and middle-income countries, only 5-15% of the people who need assistive devices and technologies are able to obtain them. Additionally, many apps come in a limited number of languages. Many require a specific app store or proprietary device. Commercial AAC solutions can be expensive, and/or have limited language support, which can render them largely inaccessible to many people in low-income countries.

Enter Cboard, an open source project (recently supported by the UNICEF Innovation Fund!) powered by people dedicated to the idea of providing a solution that works for everyone, everywhere; a free, web-based communication board that leverages the thriving open source ecosystem and the built-in functionality of modern browsers.

It’s a complex problem, but, by taking advantage of available open source software and key ways in which the web has evolved over the last couple of years (in terms of modern browser API development and web standards), we can build a free, multilingual, open source, web-based alternative. Let’s talk about a few of those pieces — the Web Speech API, React, the Internationalization API, and the “progressive web app” concept.

Web Speech API

The challenge of artificially producing human speech is not new. Speech recognition and synthesis tools have been available for quite some time already — from voice dictation software to accessibility tools like screen readers. But the availability of a browser-based API makes it possible to start looking toward producing web services that have a low barrier to entry to provide speech synthesis, and that provide a consistent experience of that speech synthesis.

The Web Speech API provides an interface for speech recognition (speech-to-text) and speech synthesis (text-to-speech) in the browser. With Cboard, we are primarily concerned with the SpeechSynthesis interface, which is used to produce text-to-speech (TTS) output. Using the API, we can retrieve information about the synthesis voices available on the device (which varies by browser and operating system), start and pause speech, etc. Browsers tend to use the speech services available by default on the device’s operating system — the API exposes methods to interact with these services. We’ve done our own mapping of some of the voice and language offerings by digesting data returned from the SpeechSynthesis interface on different devices running different operating systems, using different browsers:

You can see, for example, Chrome on MacOS shows 66 voices — that’s because it uses MacOS native voices, as well as 19 additional voices provided from the browser. (Interested to see what voices and languages are available to you? Check out

Comprehensive support for the Web Speech API is still getting there, but most major modern browsers support it. (The Speech Synthesis API is available to 78.81% of users globally at time of writing). The draft specification was introduced in 2012, and is not yet a standard.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeOperaFirefoxIEEdgeSafari332749No147Mobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox7.0-7.1NoNoNo64No React

React is a JavaScript library for building user interfaces. One of the most unambiguous insights from the 2017 “State of JavaScript” — a survey of over 23,000 developers — was that React is currently the “dominant front-end library” in terms of sheer numbers, and with high marks for usage level and developer satisfaction.

That doesn’t mean it’s the best for every situation, and it doesn’t mean it will be dominant in the long-term. But its features, and the relative ubiquity of adoption (at this point, at least), make it a great option for our project, because there is a lower barrier to entry for people to begin contributing — there is a strong community for learning and troubleshooting.

React makes use of the concept of the “virtual” DOM, where a virtual representation of UI is kept in memory. Any changes to the state of your application are compared against the state of the “real” DOM, using a “diffing” algorithm. This allows us to make efficient changes to the view layer of an application, and represent the state of our application in a predictable way, without requiring manual DOM manipulation (for the most part). React also emphasizes the use of component-based architecture.

React is supported by all popular browsers. (For some older browsers like IE 9 / IE 10, polyfills are required).

ECMAScript Internationalization API

As noted earlier, one area in which current AAC offerings fall short is broad multi-language support. The combination of the Web Speech API, the Internationalization API (and the open source offerings around it), and React, allow us to support up to 33 languages. (For reasons described earlier, this support varies between operating systems).

Internationalization is the process of designing and developing an application and its content “in a way that ensures it will work well for, or can be easily adapted for, users from any culture, region, or language.” The Internationalization API provides functionality for three key areas: string comparison, number formatting, and date and time formatting. The API is exposed on the global Intl object.

FormatJS, a collection of libraries that build on the Intl standard, has a suite of integrations with common component libraries (like React!) that ship with the FormatJS core libraries built in. We use the React integration, react-intl, which provides bindings to internationalize React apps.

Most browsers in the world support the ES Intl API (84.16% of users globally at time of writing).

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeOperaFirefoxIEEdgeSafari241529111210Mobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox10.0-10.237No4.46457 Progressive Web Apps

Progressive Web Apps (PWAs) are regular websites that take advantage of modern browser features to deliver a web experience with the same benefits (or even better ones) as native mobile apps. Any website is technically a PWA if it fulfills three requirements: it runs under HTTPS, has a Web App Manifest, and has a service worker. A service worker essentially acts as a proxy, sitting between web applications, the browser, and the network. It runs in the background, deciding to serve network or cached content based on connectivity, allowing for management of an offline experience.

Beyond those three base requirements, things get a bit more murky. When Alex Russell and Frances Berriman introduced and named “progressive web app” they enumerated ten attributes that characterize a PWA — responsive, connectivity independent, app-like, fresh, safe, discoverable, re-engageable, installable, and linkable.

This concept ends up as the key puzzle piece in our attempt to build something that solves the problems described earlier — that most existing AAC solutions can be prohibitively expensive, offer limited languages, or remain stuck in an app store or proprietary device. Using the PWA approach we can tie together the features modern browsers have to offer — the Web Speech API, Internationalization API, etc — coupled with an app-like experience regardless of operating systems, un-beholden to centralized app distribution methods, and with support for seamlessly continued offline use.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeOperaFirefoxIEEdgeSafari453244No1711.1Mobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox11.337No626457

The current state of the web provides all the foundational ingredients we need to build a more inclusive, more broadly accessible AAC solution for people around the world. In the spirit of the open web, and with a great nod to the work that has been done to codify web standards, we know that a free and open communication solution is in sight.

Sound interesting to you? We invite you to come take a look and perhaps even dig in as a contributor!


The post A Browser-Based, Open Source Tool for Alternative Communication appeared first on CSS-Tricks.

Fun, interactive updates to our Source Han Serif showcase

Nice Web Type - Tue, 03/13/2018 - 8:12am

Adobe Typekit designer Wenting Zhang’s work on the Source Han Serif website won honorable mention at Awwwards for web design. She doesn’t consider her work done, though — and the site continues to evolve with her vision in really neat ways.

When Adobe Type considered how to make a major marketing splash with the companion to the enormously popular Pan-CJK Source Han Sans, Wenting was tasked with designing a standalone website to showcase the type. Wenting is a designer at Typekit, and also a student of type design at the Type@Cooper program in New York, so this was a great opportunity for her to unleash her design skills in a typographic context.

From display image to typographer’s tool

Wenting wanted to present the beautiful Pan-CJK typeface in a more interactive manner. The glyph map at the top, which displays the most frequently used glyphs for each featured language, was a natural point to experiment with hover state behavior.

Wenting got feedback that people wanted to examine the glyphs in closer detail. She’d had something like that in mind herself, imagining that the static cells could be active links. After she made a round of updates, tapping on each character cell now toggles a detail view modal.

Why show the glyphs in so many rotated dimensions? Wenting explains that the rotated view is intended to mimic a common type design technique: designers will often flip a character around to study the spacing more closely.

Designing for regional authenticity

An important feature of Source Han Serif that informed Wenting’s design is its support for regional glyph variations. Having options to switch among regional glyph designs solves a major problem for users of Pan-CJK type, and currently Source Han Serif is one of few typefaces in the world that can solve it. As a result the Source Han Serif site is an important showcase for everything a well-considered Pan-CJK typeface can do.

One example of the regional differences in a shared glyph.

Why are these regional differences so important to highlight? Wenting explains that the attention to detail makes a huge difference when people read text written with a typeface that does — or doesn’t — support the variations. Without that support, the reading experience simply feels “off.” Some readers might even say that a page using (for example) Japanese glyph variants when writing in Simplified Chinese “feels Japanese,” despite the glyphs being technically shared between both languages.

Wenting’s goal was to make the site even more sensitive to specific language considerations and design conventions. As other Chinese, Japanese, and Korean-speaking designers saw the initial version of the site, Wenting got valuable feedback that she decided to incorporate into her updates.

For example, she made more use of vertical layout for Traditional Chinese and Japanese designs, while taking a slightly different tack with Korean (where vertical layout is mostly obsolete) and Simplified Chinese (where it is less common).

Type testing from a designer’s point of view

Wenting plugged in more functionality for the originally-static illustration showing Source Han Serif in a design application. Changing the font selected in the “menu” now results in actual changes to the glyphs just as it would in a real design application — not only weight variations but also reflecting the differences between Korean, Chinese, and Japanese variants of the same glyph. As with other aspects of the site that take the regional glyph variations into account, Wenting said this makes for a much more authentic experience for designers working with different East Asian languages, and is more inclusive of the diversity among pan-CJK type users.

Visual dynamics

Wenting was pleased to further develop some of her initial ideas for the site, and as she scanned the site with fresh eyes she came up with even more ideas to make it more dynamic and add visual interest — such as the staggered animation that brings a neat cascading effect to the chart showing different weights for the typeface.

Hidden in the site is a simple game to spot the differences between region-specific glyphs. This was actually part of the site at the original time of launch. It’s a fun feature and showcases the attention to detail that went into the typeface design.

One last feature Wenting is excited to share can be seen just as you load the site for the first time. An animated cube appears, and if you read Chinese, the glyphs on each side of the cube are intended to be read in sequence as “??????????” repeatedly. Playing with name of the typeface in Chinese (????), the cube represents Wenting’s best wishes for the users of Source Han Serif: “May Source Han Serif bring you a stream of ideas.”

Visit the Source Han Serif website to see Wenting’s work for yourself.

Chrome DevTools “Local Overrides”

Css Tricks - Tue, 03/13/2018 - 7:38am

There's been two really interesting videos released recently that use the "Local Overrides" feature of Chrome DevTools in order to play with web performance without even touching the original source code.

The big idea is that you can literally edit CSS and reload the page and your changes stick. Meaning you can use the other performance testing tools inside DevTools to see if your changes had the effect you wanted them to have. Great for showing a client a change without them having to set up a whole dev environment for you.

Direct Link to ArticlePermalink

The post Chrome DevTools “Local Overrides” appeared first on CSS-Tricks.

Notched Boxes

Css Tricks - Tue, 03/13/2018 - 7:30am

Say you're trying to pull off a design effect where the corner of an element are cut off. Maybe you're a Battlestar Galactica fan? Or maybe you just like the unusual effect of it, since it avoids looking like a typical rectangle.

I suspect there are many ways to do it. Certainly, you could use multiple backgrounds to place images in the corners. You could just as well use a flexible SVG shape placed in the background. I bet there is also an exotic way to use gradients to pull it off.

But, I like the idea of simply taking some scissors and clipping off the dang corners. We essentially can do just that thanks to clip-path. We can use the polygon() function, provide it a list of X and Y coordinates and clip away what is outside of them.

Check out what happens if we list three points: middle top, bottom right, bottom left.

.module { clip-path: polygon( 50% 0, 100% 100%, 0 100% ); }

Instead of just three points, let's list all eight points needed for our notched corners. We could use pixels, but that would be dangerous. We probably don't really know the pixel width or height of the element. Even if we did, it could change. So, here it is using percentages:

.module { clip-path: polygon( 0% 5%, /* top left */ 5% 0%, /* top left */ 95% 0%, /* top right */ 100% 5%, /* top right */ 100% 95%, /* bottom right */ 95% 100%, /* bottom right */ 5% 100%, /* bottom left */ 0 95% /* bottom left */ ); }

That's OK, but notice how the notches aren't at perfect 45 degree angles. That's because the element itself isn't a square. That gets worse the less square the element is.

We can use the calc() function to fix that. We'll use percentages when we have to, but just subtract from a percentage to get the position and angle we need.

.module { clip-path: polygon( 0% 20px, /* top left */ 20px 0%, /* top left */ calc(100% - 20px) 0%, /* top right */ 100% 20px, /* top right */ 100% calc(100% - 20px), /* bottom right */ calc(100% - 20px) 100%, /* bottom right */ 20px 100%, /* bottom left */ 0 calc(100% - 20px) /* bottom left */ ); }

And you know what? That number is repeated so many times that we may as well make it a variable. If we ever need to update the number later, then all it takes is changing it once instead of all those individual times.

.module { --notchSize: 20px; clip-path: polygon( 0% var(--notchSize), var(--notchSize) 0%, calc(100% - var(--notchSize)) 0%, 100% var(--notchSize), 100% calc(100% - var(--notchSize)), calc(100% - var(--notchSize)) 100%, var(--notchSize) 100%, 0% calc(100% - var(--notchSize)) ); }

Ship it.

See the Pen Notched Boxes by Chris Coyier (@chriscoyier) on CodePen.

This may go without saying, but make sure you have enough padding to handle the clipping. If you wanna get really fancy, you might use CSS variables in your padding value as well, so the more you notch, the more padding there is.

The post Notched Boxes appeared first on CSS-Tricks.

A Better Sketch File, a Better Designer, a Better You

Css Tricks - Mon, 03/12/2018 - 9:27am

I’ve been thinking about this post by Isabel Lee for the last couple of weeks — it’s all about how we should be more considerate when making designs in Sketch. They argue that we’re more likely to see real efficiency and organizational improvements in our work if we name our layers, artboards, and pages properly. Isabel writes:

Keeping a super organized Sketch file has helped me smooth out my design process and saved me time when I was trying to find a specific component or understand an archived design. For instance, I was looking for an icon that I used six months ago and it was (relatively) easy to find because all my artboards and layers were well-named and grouped reverse-chronologically. I was also able to cross-reference it with my meeting notes from around that time. If I hadn’t done any of that work (thanks Past Isabel!), I probably would’ve had to dig through all my old designs and look at each layer. Or worse?—?I would’ve had to recreate that icon.

Since I read this I’ve been doing the same thing and effectively making “daily commits” with the naming of my pages and it’s been genuinely helpful when looking back through work that I’ve forgotten about. But what I really like about this tidy-up process is how Isabel describes the way in which they could easily look back on their work, identify weaknesses in their design process, and how to become a better designer:

Aside from making it easier to find things, it’s also helped me cultivate good documentation habits when I want to analyze my old work and see where I could’ve made improvements. I revisited one of my old Sketch files and realized that I didn’t do enough research before diving into a million iterations for an initial idea I had.

Direct Link to ArticlePermalink

The post A Better Sketch File, a Better Designer, a Better You appeared first on CSS-Tricks.

Consistent Design Systems in Sketch With Atomic Design and the Auto-Layout Plugin

Css Tricks - Mon, 03/12/2018 - 3:15am

Do you design digital products (or websites) and hand design files off to developers for implementation? If you answered yes, settle in! While the should-designers-code debate rages on, we're going to look at how adding a methodology to your design workflow can make you faster, more consistent, and loved by all developers... even if you don't code.

Let's dig in!

Why a methodology?

In the development world, it seems like at least half of your career is about staying up to date with new tech and leveling up your skills. While the pace may not be quite as frantic in the design landscape as it is in development, there definitely has been a huge shift in tools over the past three years or so.

Tools like Sketch have made a lot of the old pain of working in design files a thing of the past. Smart combinations of text styles, object styles, symbols, and libraries now mean sweeping changes are just one click away. No more picking through 40 Photoshop layers to make a single color change.

Yet, sweeping color changes in a marketing landing page is no longer the biggest design challenge. Design and development teams are now expected to deliver complex interfaces loaded with interaction and conditional states... for every device available now and the next day. Working as both a designer and developer, I have seen the workflow friction from both sides.

Beyond tools, designers need an approach.

Thinking in terms of "components"

If you work in the tech space in any capacity, you have likely heard of development frameworks such as React, Angular, or Vue.

Um yeah, I'm a designer so that doesn't really concern me, bye.

Kinda. But if you're hoping to do design work for modern digital products, there is a pretty big chance that said products will be built using one of these frameworks. No one expects an architect to build the structures themselves, but they better have a high-level understanding of the what the tools are and how they will be used.

So here's what you need to know about modern front-end frameworks:

They have brought on a paradigm shift for developers in which products are built by combining a series of smaller components into complex systems which can adapt to different contexts and devices. This makes the code easier to maintain, and the entire system more flexible.

For a variety of legitimate reasons, many designers have not caught on to this paradigm shift as quickly as developers. We are missing a mental model for creating the pieces that make up these interfaces independently from their environment/pages/screens/viewports etc.

One such approach is Atomic Design.

What is Atomic Design?

First coined by Brad Frost, Atomic Design is a methodology which tries to answer a simple question: if hundreds of device sizes mean we can no longer effectively design "pages," then how do we even design?

The answer lies in breaking down everything that could make up a "page" or "view" into smaller and smaller pieces, creating a "system" of blocks which can then be recombined into an infinite number of variations for our project.

You can think of it like the ingredients in a recipe. Sure, you could make muffins, but you could just as easily make a cake with the same list of ingredients.

Brad decided to use the chemistry analogy, and so he proposes that our systems are made up of:

  • Atoms
  • Molecules
  • Organisms

For the sake of simplicity, let's take a look at how we might apply this to a website.


Atoms represent the smallest building blocks which make up our user interfaces. Imagine a form label, a form input, and a button. Each one of those represents an atom:

A header, text block, and link each serve as atoms. Molecules

Molecules are simply groups of atoms combined to create something new. For our purposes, we can think of molecules as groups of disjointed UI pieces which are combined to create a meaningful component.

The atoms come together to form a "card" component. Organisms

Organisms are made up of groups of molecules (or atoms or other organisms). These are the pieces of an interface which we might think of as a "section." In an invoicing application, an organism could be a dashboard combining a "new invoice" button (atom), a search form (molecule), a "total open" card (molecule), and table listing overdue invoices. You get the idea.

Let's look at what a "featured block" organism might look like in our simple website:

A header (atom), three cards (molecules), an image (atom), and a teaser (molecule) are combined to form one featured block organism. Using stacks for consistency

So, now that we have a mental model for the "stuff," how do we go about creating these building blocks? Sketch is great out of the box, but the plugins available for it provide huge performance tools… just like text editors for developers. We will be using Sketch's built-in symbols tools, as well as the amazing Stacks feature from Anima App's Auto-Layout plugin.

Using these tools will bring some priceless benefits which I will point out as we go, but at the very least you can count on:

  • better design consistency and faster iteration
  • a sanity check from using consistent spacing multipliers
  • faster reordering of content
  • help identifying design problems quickly and early on
What exactly are stacks?

If you've ever heard developers excitedly talk about flexbox for building layouts in CSS, then you can think of stacks as the Sketch equivalent. Stacks (like flexbox) allow you to group a series of layers together and then define their spacing and alignment on a vertical or horizontal axis.

Here we group three items, align them through their center, and set 48px vertical space between each one:

A simple stacked folder aligning and distributing three items.

The layers will automatically be group into a blue folder with an icon of vertical or horizontal dots to show what kind of stack you have.

Look at that! You just learned flexbox without touching a line of code. 😂

Nested stacks

The real power of stacks comes from nesting stacks inside other stacks:

Stacks can be nested inside of each other to create complex spacing systems.]

Here, we can see a card component made up of multiple stacks:

  • card__cta link from the previous example.
  • card__copy stack which handles the alignment & space for the header and text.
  • card__content which controls the spacing and alignment between the card__cta and card__copy stacks.
A quick note about layer naming

I almost always use the BEM naming convention for my components. Developers appreciate the logic when I have to to hand off design files because it often aligns with the naming conventions they are using in code. In the case where I'm developing the project myself, it gives me a bit of a head start as I've started thinking about the markup at the design stage.

If that's super confusing, don't worry about it. Please just make your colleagues' job a little easier by organizing your layers and giving them descriptive names.

Stacks shmacks, I have great attention to detail and can do all this manually!

You're absolutely right! But what about when you have carefully laid out 10 items, all of varying sizes, and now they need extra space between them? Or, you need to add a line of text to one of those items? Or, you need to split content into three columns rather than four?

That never happens, right? 😱

One of two things usually happens at this stage:

  1. You start manually reorganizing all the things and someone's paying for wasted time (whether it’s you or the client).
  2. You kinda fudge it… after all, the developer surely knows what your original intentions were before you left every margin off by a couple pixels in your layout. ¯\_(?)_/¯

Here's what stacks get you:

  • You can change the alignment or spacing options as much as you like with a simple value adjustment and things will just magically update.
  • You can resize elements without having to pick at your artboard to rejig all the things.
  • You can reorder, add, or remove items from the stack folder and watch the items redistribute themselves according to your settings—just like code. 🎉

Notice how fast it is to edit content and experiment with different layouts all while maintaining consistency:

Stacks and symbols make experimentation cheap and consistent.

OK, so now we know why stacks are amazing, how do we actually use them?

Creating stacks

Creating a stack is a matter of selecting two (or more) layers and hitting the stacks folder icon in the inspector. From there, you can decide if you are stacking your layers horizontally or vertically, and set the distance between the items.

Here’s an example of how we’d create an atom component using stacks:

Creating a horizontal stack with 20px spacing between the text and icon.

And, now let’s apply the stacks concept to a more complex molecule component:

Creating a card molecule using nested stacks. Creating symbols from stacks

We’ve talked about the many benefits of stacks, but we can make them even more efficient by applying Sketch’s symbol tool to them. The result we get is a stack that can be managed from one source instance and reused anywhere.

Creating an atom symbol

We can grab that call-to-action stack we just created and make it a symbol. Then, we can use overrides for the text and know that stacks will honor the spacing:

Creating a symbol from a stack is great for ensuring space consistency with overrides.

If I decide later that I want to change the space by a few pixels, I can just tweak the stack spacing on the symbol and have it update on every instance 🎉

Creating a molecule symbol

Similarly, I can group multiple stacked atoms into a component and make that into a symbol:

Creating a card symbol from our stacks and call-to-action symbol. Symbols + stacks = 💪

Wouldn't it be nice if we could maintain the spacial requirements we set regardless of the tweaks we might bring to them down the line? Yes!

Replacing one item with another inside a component

Let's assume our card component now requires a button rather than a link as a call-to-action. As long as we place that button in the correct stack folder, all the pixel-nudging happens automagically:

Because our symbol uses stacks, the distance between the copy and the call-to-action will automatically be respected. Editing molecules and organisms on the fly 🔥

You might be thinking that this isn't a huge deal and that adjusting the tiny spacial issue from the previous example would have taken just a moment without stacks. And you wouldn't be wrong. But let's refer back to our notions about atomic design for a moment, and ask what happens when we have far more complex "organisms" (groups of atoms and molecules) to deal with?

Similar to our mobile example from the beginning, this is where the built-in power of stacks really shines:

Stacks and symbols makes experimentation cheap and consistent.

With a library of basic symbols (atoms) at our fingertips, and powerful spacing/alignment tools, we can experiment and create endless variations of components. The key is that we can do it quickly and without sacrificing design consistency.

Complex layouts and mega stacks

Keeping with the elements we have already designed, let's see what a layer stack might look like for a simple marketing page:

An example of an expanded layer stack.

Don't let the initial impression of complexity of those stacks scare you. A non-stacked version of this artboard would not look so different aside from the color of the folder icons.

However it's those very folders that give us all the power:

Layout experimentation can be fast and cheap! We may not need to code, but we have a responsibility to master our tools for efficiency and figure out new workflows with our developer colleagues.

This means moving away from thinking of design in the context of pages, and creating collections of components… modules… ingredients… legos… figure out a metaphor that works for you and then make sure the whole team shares the vocabulary.

Once we do this, issues around workflow and collaboration melt away:

Speed and Flexibility

Carefully building components with symbols and using automated and consistent spacing/alignment with stacks does require a bit of time investment upfront. However, the return of easy experimentation and ability to change course quickly and at low cost is well worth it.

Consistency and UX

Having to think about how elements work as combinations and in different contexts will catch UX-smells early and expose issues around consistency before you’re 13 screens in. Changing direction by adjusting a few variables/components/spacing units beats nudging elements around an artboard all day.

Responsibility and Governance

A single 1440px page view of the thing you are building simply does not provided a developer with enough context for multiple screens and interaction. At the same time, crafting multiple high fidelity comps one tiny element at a time is a budget buster (and this is particularly true of app designs). So, what tends to happen on small teams? The developer gets the one gorgeous 1440px view… aaaaand all the cognitive overhead of filling in the gaps for everything else.

Providing the details is our job.

Atomic design gave us speed, creative freedom, and flexibility. It changed everything.”

—From the forward of Atomic Design

If we work with developers on digital products, we should be excited about learning how the sausage is made and adapt our approach to design accordingly. Our tools may not evolve quite as quickly as JavaScript frameworks, but if you haven’t taken a peek under to hood of some of these apps in the last couple of years, this is a great time to dig in!

The post Consistent Design Systems in Sketch With Atomic Design and the Auto-Layout Plugin appeared first on CSS-Tricks.

CSS-Tricks Chronicle XXXIII

Css Tricks - Sun, 03/11/2018 - 4:48am

It's been many months since our last CSS-Tricks Chronicle. As a reminder, these are just little roundups of news of a slightly more personal nature and that of the site itself and surrounding projects.

Last update, I wasn't even a dad yet! That's changed 😍. My daughter is going in for her four-month checkup today!

I'm also working out of a brand new office here in Bend, Oregon. We split the space with CraftCMS. It's the first time I've had an office that I have real (part) ownership over. We've built out a kitchen area in it and are decorating it and fleshing it out to be useful and fun for all of us.

One particularly cool thing, we splurged on a VocalBooth. It's got the whole medium-fancy podcasting setup in there, so it's pretty darn good sound quality for the podcasting we do. Plus it's a nice place to take a call or meeting as well.

I made a round of updates to my microsite The Power of Serverless for Front-End Developers. My goal with that site is to explain the idea to the best of my ability and how it can be a useful thing to know about particular for front-end developers to do more with the skills they already have. Once you're sold on that, it's still a huge world to wrap your head around. One of the issues is understanding how many different services are out there and what roles they are trying to play. So the Services section is decently fleshed out and organized to help with that.

Over on CodePen Radio, I got to chat about all this with Marie Mosley. That's the first time I've really talked about it, so it kinda resulted in a big ol' thought dump.

I've had so many thoughts about this "serverless" stuff, I figured I'd formulate them into a proper conference talk. I'm not doing too many of those this year, but I'll definitely be at An Event Apart Seattle (April 2-4), and Front-End Design Conference (April 25-27). Come!

I got to speak with Jonathan Denwood and John Locke on the WP-Tonic Podcast.

Tons going on at CodePen, of course. We're always podcasting all about that, so if you're particularly interested in behind-the-scenes CodePen, CodePen Radio is your friend. As I imagine any business, we're always hard at work on a mix of projects. Some largely internal, like rearchitechting our email system, and some directly for our users, like rewriting our realtime systems to make features like Collab Mode way better.

As the year flipped over, of course we rounded up the Most Hearted list, so if you missed that, check it out.

ShopTalk just had a major milestone! Our 300th episode! Dave and I take the opportunity to talk about then and now. We've had loads of fun and educational episodes this year though, so if you just pick and choose episodes to listen to, I bet you'll find one worth a crack.

The post CSS-Tricks Chronicle XXXIII appeared first on CSS-Tricks.

Design Microsites

Css Tricks - Sat, 03/10/2018 - 7:31am

Google, Airbnb, Slack, MailChimp, Facebook, Etsy, IBM, Dropbox... everybody has a design site these days.

Direct Link to ArticlePermalink

The post Design Microsites appeared first on CSS-Tricks.

Extinct & Endangered

Css Tricks - Fri, 03/09/2018 - 10:07am

I've been watching a lot of nature documentaries lately. I like how you can either pay super close attention to them, or use them as background TV. I was a massive fan of the original Blue Planet, so it's been cool watching the Blue Planet II episodes drop recently, as one example. A typical nature documentary will always have a little look how bad we're screwing up the environment twist, which is the perfect time and place for such a message.

Speaking of perfect time and place, why not remind ourselves of all the endangered animals out there with placeholder images! That's what Endangered Species Placeholders is. It's like PlaceKitten, but for environmental good.

I also just came across this free icon set of extinct animals. 😢

Direct Link to ArticlePermalink

The post Extinct & Endangered appeared first on CSS-Tricks.

Vox Accessibility Guidelines

Css Tricks - Fri, 03/09/2018 - 10:05am

I remember seeing these accessibility guidelines from Vox a while ago but it’s still interesting to go over them again today and see if there’s anything missing from my own process when it comes to improving accessibility.

And there’s an awful lot to remember! Color contrast, alt-text, keyboard navigation, focus states, and ARIA attributes are only a small snippet of the total number of things we ought to be mindful of when designing websites and so this checklist is certainly helpful for giving us all a good nudge from time to time.

Plus, it's worth remembering that there are small tweaks we can make and ways to advocate for improved accessibility in our projects.

Direct Link to ArticlePermalink

The post Vox Accessibility Guidelines appeared first on CSS-Tricks.

Some Things About `alt` Text

Css Tricks - Fri, 03/09/2018 - 4:38am

I'm sure you know about alt text. It's the attribute on the image tag that has the important task of describing what that image is for someone who can't see it for any reason. Please use them.

I don't want to dimish the please use them message, but some interesting alt-text-related things have come up in my day-to-day lately that are related.

When you don't

Hidde de Vries wrote You don't always need alternative text recently:

But when an icon has a word next to it, for example ‘Log out’, the icon itself is decorative and does not need an alternative text:

<button type="button"><img src="close.svg" alt="" /> Close</button>

In this case we can leave the alt attribute empty, as otherwise a screenreader would announce ‘button - close close’.

I would think in a perfect world because that icon is entirely decorative that applying it via CSS would be ideal, but the point stands: if you have to use an image, alt text hurts more than it helps here.

Can we get it for free?

Computers are pretttttty smart these days. Perhaps they could look at our images and offer up descriptions without us manually having to type them.

&#x1f525; I think I used machine learning to be nice to people! In this proof of concept, I’m creating dynamic alt text for screenreaders with Azure’s Computer Vision API. &#x1f4ab;

— Sarah Drasner (@sarah_edo) November 13, 2017

Sarah's demo uses a Computer Vision API to do that.

What about figcaption?

I hate to say it, but I'm not particularly good at writing alt text descriptions for images in blog posts right here on CSS-Tricks. It's a problem we need to fix with process changes. We do often use <figcaption> though to add text that's related to an image. The way that text is often crafted feels like alt text to me. It describes what's going on in the image.

I was asking around about this, and Zell Liew told me he does the same thing:

I actually have the same question. Most of my figcaptions are used to describe the image so readers understand what the image is about.

In my mind, I figured it would be worse to drop the exact copy from the figcaption into the alt text, as someone who was reading alt text would then essentially read the same description twice.

I also talked to Eric Bailey who had an interesting idea.

<figure> <img src="screenshot.png" alt="Screenshot of Chrome displaying a split view. On the left is a page full of image thumbnails comparing pre and post-optimization filesize. On the right is Chrome developer tools showing paint rasterize duration for the images. With a 6x CPU slowdown, the longest Paint Raster took 0.27ms, AKA 0.00027 seconds."> <figcaption aria-hidden="true"> With a 6x CPU slowdown, the longest Paint Raster took 0.27ms, AKA 0.00027 seconds. </figcaption> </figure>


  • Preserves figure styling
  • Avoids nulling alt, which can be problematic for some screen readers
  • Keeps the description close to the content and communicates point to SR users
  • Communicates significant takeaway to visual readers without duplicating reading for SR users
  • Uses an aria property designed to be used outside of forms

I'd stress that he considered this just an idea and it hasn't been heavily vetted by the larger accessibility community. If there are any of you out there reading, what do you think?

Eric's demo used a more verbose alt text than the figcaption, but it seems like the pattern would be fine even if they were identical.

Little reminder: Twitter has alt text

But you need to enable the feature.

The post Some Things About `alt` Text appeared first on CSS-Tricks.

Screen Recording Utilities for macOS

Css Tricks - Thu, 03/08/2018 - 10:25am

I record quite a few short little videos. Sometimes for use demonstrating bugs or weirdnesses. Sometimes right here for the blog. A lot of times for Instagram or other social media.

Allow me to get SUPER NITPICKY about what I like.

  • Multiple formats. Sometimes you need a GIF. Sometimes you need an MP4. Sometimes you need both. It's ideal if the software can export as either or both.
  • Easily resizeable recording area. If you need to record the entire screen, fine, but I feel like that's the job for more full-blown screencasting apps. More often, I need to record a smaller bit of the screen. Ideally, I can drag over the portion I want, but the more control the better.
  • Aspect ratios and saved sizes. Speaking of control, it's likely I might want a square recording (like if it's going to Instagram) or I might want a 16:9, a common aspect ratio for TV's and web video. Ideally, the software helps me get there quickly.
  • Cursor/clicking, or not. Sometimes the point of a video is to demonstrate something, which might require showing the cursor and interactions like clicks. Ideally, that is available but turn-off-able.
  • Editing after recording. The chances of getting a perfect take are rare. More commonly, I'd like to adjust the start and end time of the recording. Since it's likely the GIF or video is meant to repeat, playing the recording as this is happening is ideal.
  • Configurable shortcuts. I'd ideally like to hit a keyboard command to fire up the app, select a recording area, and go.
  • Audio or no audio. It should be possible to record sound, clear if I am or not, and configurable.
  • Cost. This is just informational as it's typically a factor. Generally I like to pay for things as it can be a good indicator of quality and support. But as we all known, open source can be incredible, and incentivized companies can do well making free products, too.
  • Retains history. Maybe I need to re-cut it. Maybe I lost my export somehow. Maybe the app weirdly quit. Ideally, I'd like some history so I can go back to some older recordings and export another copy.
GIPHY Capture The green box there is the area of the screen GIPHY Capture records.

This is a great idea for a company like GIPHY to build, and they've done a fine job here. Best of all, I've watched it evolve over time to get more and more useful. As a cool bonus feature, you can add captions at specific points in the recording.

My main gripe is the two-window system. The green-box window has recording and history, then a second window for editing and exporting. That alone is no big deal, but when you are editing, you often want to be gone with the green box. But closing the green box means quitting the whole app.

Multiple formats GIF, MP4, or "Batch" which outputs a folder with both. Easily resizeable recording area Position and size the green box over the area you want to record. Aspect ratios and saved sizes No aspect ratios, but you can specifiy pixel width/height and it will save your recently used ones. A bit hidden, you have to click the pixel dimensions in the lower right to access it. Cursor/clicking Recording the cursor is on/off setting. If on, it adds a circle around the cursor when you click. Editing after recording Handles at the beginning and end of the timeline allow you to drag them inward to crop the clip. Very nicely handled (get it?) — I think this might be the best take on editing. File size control You can choose from a handful of options for both pixel size and frame rate to control size.

Not as fine-grained as you might want but likely fits most needs. Configurable shortcuts When the app is open, you can set a letter or number as a key command to start/stop recording. Audio or no audio No audio recording at all Cost Free Retains history I'm not sure how far back the history goes (it's a bit hard to navigate beyond what you can see) but the lower bar of the green recording window gives history access to the last few very easily. Batch exporting is a clever feature. Sometimes I really do need both types (GIF and video), and that need is likely to increase. Kap

I think Kap is my favorite one. At least it is today. It's quite polished, and also open source, perhaps as a bit of marketing for the agency it comes from.

Kap is at version 2.0 right now, and I quite like it. I had 1.0 and aborted pretty quickly. I can't remember why exactly, but it didn't measure up to other options. Version 2.0 is perhaps best-of-breed. I'm a fan of the fact that it's a menu bar app, so it is ready all the time instead of something I need to launch.

Its fancy bonus feature is installable export locations, like uploading to Cloudinary or S3.

The keyboard shortcut is one thing that (and this is weirdly unique to me) really bugs me. It actually keeps me from having it open all the time, because Command-Shift-5 is CodePen's command for re-running, which I use all the time. Configurability, please!

Multiple formats The most formats! GIF and MP4, but also WebM and APNG. Cool, but you can only export one at a time. Easily resizeable recording area Clicking the record button gives you little black-white dashed lines you position and size over the recording area. If GIPHY Capture's green screen is papa bear (too much), this is mama bear (too little). There is probably a just right baby bear in there somewhere. Aspect ratios and saved sizes Sizing is a first-class citizen here, giving you a dropdown for aspect ratio or controls for exact sizes (that it remembers). Cursor/clicking Under preferences, you can flip cursor recording on and off (and separately from click highlighting). Editing after recording Drag handles from the start or end inward to edit. File size control It's a big strange. There is an FPS control buried in settings to adjust the frame rate, but then on the editing screen before you export, you only get to pick between 30 and 15, so it's not clear what happens if you've adjusted it in settings to something other than those. Configurable shortcuts Weirdly, it's Command-Shift-5, which it doesn't tell you, allow you to turn off, or configure. Audio or no audio One click to turn on and off right before you record or after you record. Cost Free and open source. Retains history No history, but warns you before you close an editing window so you don't accidently lose recordings. LICEcap

Old school! LICEcap is the app that opened my eyes to the idea that these apps were even a thing. Perhaps the first of its kind.

Multiple formats GIF only Easily resizeable recording area The empty frame window might be the most clear UI out of all of them. Aspect ratios and saved sizes Neither, but it is easy to manually resize or type in pixel dimensions manually Cursor/clicking You decide if you want it right before you record. Editing after recording None File size control You can choose the FPS as you record, so you have good control, but it can't be changed after recording. So if it ends up too large or too small, you have to re-record. Configurable shortcuts None Audio or no audio As it's GIF only, there is no audio. Cost Free Retains history No Droplr Menu bar app

Droplr absolutely has the power to record quick screencasts, but it's much more limited than these others. What it does offer is a very quick way to get your screencasts up onto the web in a permanent and shareable way very quickly. If that's the most important thing to you, you'd be in good hands.

Multiple formats GIF or MOV Easily resizeable recording area Every time you record you have to drag over the area you want to record. Aspect ratios and saved sizes Easy to select a recording area, but it doesn't save sizes, tell you the dimensions of your selected area, or help with aspect ratios. Cursor/clicking Automatically includes cursor and click highlighting. Editing after recording None. You just choose GIF or MOV and it auto-uploads it. The ability to at least save locally before uploading would be nice. File size control None Configurable shortcuts You can pick a custom keyboard command for screencasts, along with different key commands for everythinge else Droplr does. Audio or no audio As you upload, it gives the impression that videos automatically include sound. But, you can turn off audio recording in preferences. Cost Freemium. If you need unlimited length screen recordings, it's $8.29/month. Retains history Yep, through the Droplr service, you'll have a complete history of all recordings. CloudApp

Like Droplr, CloudApp will help you record a screencast, but it's all about getting that screencast uploaded to their service so you can share it from there. That can be awfully handy, but also get in the way when you just want to work locally. You can set a preference to save the GIF and movie record locally, but it's a PRO feature.

Multiple formats GIF or MOV, which you pick before you record. Easily resizeable recording area Drag over the area you want. Aspect ratios and saved sizes Also like Droplr, you drag over the area you want, but it doesn't tell you the dimensions as you are doing it, allow to specify or adjust that size with numbers or help with aspect ratios. Cursor/clicking Option in preferences. Editing after recording No File size control In preferences, you can configure GIF FPS at 3, 6, or 12. Configurable shortcuts Lots of options for all the types of screenshots or recordings you can do. Audio or no audio Button to turn on or off (and select source) right before you record. Cost Freemium. Pro plans start at $8/month. Very heavy on the upgrade triggers. Retains history On their service. Quicktime Player

It's worth knowing that macOS has a built-in way to record the screen, and it's not half bad. Pop it open and do File > New Screen Recording and you have a decent little tool for video recordings.

Multiple formats Just video. Easily resizeable recording area Drag over the area you want to record (or just click to record the whole screen). Aspect ratios and saved sizes No help with saved sizes, specifying the size you want, previously used sizes, or aspect ratios. Cursor/clicking You automatically get the cursor, but no click highlighting. Editing after recording Command-T gives you a Trim dialog for editing the start and end points. File size control You can export the video in 4K, 1080p, 720p, etc. If the video isn't big enough, the unavailable options are grayed out. Configurable shortcuts Not configurable, but it does have default shortcuts for when the app is open and active. Audio or no audio Beside the red button that starts the recording there is a dropdown to select an audio source (or none). Cost Free, as in comes with macOS. Retains history Only if you save the files. It will prompt you to save before closing them. Gifox

Gifox was unknown to me before I started looking around for this post. It's pretty great! Very modern. Lots of options. Fairly priced. Plus a few pretty neat features.

Multiple formats Only GIF, which is unfortunate as it might be this app's only weakness. Easily resizeable recording area Drag to record an area (helps you with coordinates and sizing) or record specific windows (nice touch). Aspect ratios and saved sizes No aspect ratios, but it does allow you to lock the size so that subsequent recordings open up at exactly the same size. Cursor/clicking Option in settings. Editing after recording None File size control In settings you can control recording and playback FPS. Configurable shortcuts Very helpful with shortcuts. Little stuff like the spacebar to start/stop recording, but also a bunch of configurable ones in settings. Audio or no audio GIF only, so no audio. Cost Free version, but $4.99 for the licensed version which you'll probably want because it watermarks the recording otherwise. Retains history Automatically saves all recordings, so you can't lose them unless you trash them. Also has integrations with Dropbox, Google Drive, and Imgur so it can push directly there. Screenflow

Screenflow is really beefy screencasting software. All the stuff we've looked at so far is for little tiny quicky stuff. Screenflow is for long-form, edited, fancy screencasts. You can use it for little stuff, but it would be overkill and all the control would probably get in the way more than help. But if you need lots of control, it's fantastic.

Multiple formats Video is the primary target here, but the latest version does now have animated GIF export. Easily resizeable recording area Screenflow turns this on it's head. Generally, you record the entire screen, then during editing, you crop down to what you need. Newer versions let you scope down the recording area before you record, but the old paradigm is still there and probably a smart way to work in general. Aspect ratios and saved sizes Lots of control. It defaults to resizing as an aspect ratio when you crop after recording, but you can change it with hard pixel values if you wish, or choose from presets. Cursor/clicking Loads of control here. As you're editing, you can add action points that allow you to focus on the cursor with various effects, like graying out the rest of the screen. Editing after recording This is the main point of Screenflow. File size control Lots of exporting control for size, speed, and quality. Configurable shortcuts Massive set of configurable keyboard commands. Audio or no audio You choose what audio sources you want to record when you record. You can always remove those tracks during editing, or edit the audio just as you do the video. Cost Starts at $129.99. Retains history Only what you save. The Graveyard?
  • Screeny. Looks pretty nice, but also looks like it hasn't been touched in five years and I didn't wanna spend $14.99 when there seems to be a lot of good modern alternatives.
  • Recordit. Looks pretty similar to some of these others — notably CloudApp and Droplr — as it has a hosted service. But also sorta looks limited and abandoned.
  • GifGrabber. Looks pretty good and it's free! Just also feels a bit abandoned and only does GIF. This is the one that turned into GIPHY Capture.

The post Screen Recording Utilities for macOS appeared first on CSS-Tricks.

Women designers on Typekit: A few highlights for International Women’s Day

Nice Web Type - Thu, 03/08/2018 - 8:27am

Most people read words without giving the typeface — and its designer — a second thought. We are not those people. And when we think about type designers, we know that there aren’t as many women represented as there could be.

So if you’re looking for a subtle way to support women in a historically male-dominated field, here’s just a sampling of the typefaces they’ve been responsible for. Use them in honor of International Women’s Day if you like, though we suspect they’ll look great all year round.

For fantastic display headings

Milka from Lettersoup (based on design by Milka Peikova)

Milka is a stencil alphabet originally designed in 1979 by Bulgarian artist Milka Peikova. She worked with the team at Lettersoup to create this digital version, which includes her clean original stencil along with five other styles — so you can play with different textured effects to get just the right look.

Bely by Roxane Gataud

Bely by Roxane Gataud. See Behance project page.

The Regular and Bold weights of Bely show only a hint of what gets unleashed in Bely Display. All in all, this is a terrifically fun typeface when you need to go big, and a well-balanced serif at all weights. We weren’t sure whether to group this one with the text faces or the display faces. Either way, Bely handles (and even embraces) extremes with an immense grace.

Eskapade Fraktur by Alisa Nowak

Eskapade Fraktur (top) and Regular (bottom) by Alisa Nowak.

Eskapade Regular seems like a perfectly normal serif text face, but Eskapade Fraktur is a whole other beast. This is a great choice for the Gothic-storytime feel that blackletter type encapsulates.

Japanese brush style

Kazuraki by Ryoko Nishizuka

Ryoko Nishizuka’s Kazuraki has won awards twice over, placing in the 2002 Morisawa type competition and then in 2010 winning a Typeface Design award from the Type Directors Club. Kazuraki is modeled after the calligraphy of 12th century poet Fujiwara-no Teika.

Learn more from Ryoko about her design work in this 2014 interview — and if you need a different style of Japanese type for editorial settings, her latest release Ten Mincho is a great option.

Indic scripts

Last year we profiled Fiona Ross, a notable designer in this space and frequent consultant for much of the Indic type produced at Adobe — such as Adobe Kannada by Erin McLaughlin.

Adobe Kannada by Erin McLaughlin

The “novel, adept approach” that Erin exhibited even while a typography student at the University of Reading was a decided factor in Adobe’s decision to hire her for the design of Adobe Kannada. Learn more about the design process in our release from 2015. (Also see her 2010 Devanagari typeface, Katari.)

Serifs & Sans

Chaparral by Carol Twombly

Screenshot from the Gridset Demos site by Nathan Ford.

Chaparral is a slab serif, but only if you really look for its “slabby” character; the overall design feels much lighter than would be typical for this style. It’s a fantastic typeface for long-form reading, and makes great headings as well — basically a designer’s dream serif. We geeked out about it extensively in a 2011 About Face article.

Mr Eaves by Zuzana Licko

When we interviewed Zuzana Licko in 2016, she shared an anecdote of being so absorbed by the content of a museum exhibit that it took her a moment to realize she was looking at her own typeface, Mr Eaves. Her work is extraordinarily wide-ranging, with many designs that are anything but subtle (Variex is, perhaps, Mr Eaves after a seismic event) — but if it’s clarity you need, this is a stylish choice.

Maiola by Veronika Burian

Maiola by Veronika Burian. Behance project page.

The more closely you look at Maiola, the more skillful intricacies of its construction begin to stand out — details like the asymmetrical lengths of the strokes and serifs contributing to a hand-drawn quality that aims to appear “etched rather than created digitally.” While these details add dynamism and texture to the page (and Maiola is fantastic for printed work), they won’t distract from your message.

Get the hand-lettered look

Adorn by Laura Worthington

Many flavors of Adorn, showing Adorn Smooth Serif, Pomander, and Condensed Sans.

Laura Worthington doesn’t only make script typefaces, but she does have an exceptional talent for them and Adorn is a fine example of her work. Aside from the stunning variety in the type itself, she packs plenty of extras into her fonts, too — things like ornaments and borders, which are great for giving your work a customized feel and artistic edge.

Gautreaux by Victoria Rushton

Script fonts tend to be technically complex with lots of stylistic alternates in play, but Victoria Rushton designed Gautreaux to work as simply as possible, and the result is delightful all around. Her design is styled after the practiced penmanship of another woman — Victoria’s grandmother. The full story behind that is well worth a read.

Is that all?

No! This list is not exhaustive — there are more fonts by women on Typekit than the ones highlighted here. We don’t have a firm tally on how many of the over 400 designers with fonts on Typekit are women.

We definitely recommend people support women designers by learning more about what they’re working on and investing in their projects. Here are just a few leads:

  • Victoria Rushton has compiled a more extensive list of fonts by women that goes well beyond our recommendations here.
  • For more insight into the type industry from women’s perspectives, Alphabettes is a fantastic resource.
  • If you or a woman you know is someone interested in getting into the field, apply soon to join this summer’s Type@Paris program. Our sponsorship will support the tuition for a student again this year.

Creating a Parking Game With the HTML Drag and Drop API

Css Tricks - Thu, 03/08/2018 - 4:15am

Among the many JavaScript APIs added in HTML5 was Drag and Drop (we’ll refer to it as DnD in this article) which brought native DnD support to the browser, making it easier for developers to implement this interactive feature into applications. The amazing thing that happens when features become easier to implement is that people start making all kinds of silly, impractical things with it, like the one we’re making today: a parking game!

DnD requires only a few things to work:

  • Something to drag
  • Somewhere to drop
  • JavaScript event handlers on the target to tell the browser it can drop

We’re going to start by creating our draggables.


Both <img> and <a>(with the href attribute set) elements are draggable by default. If you want to drag a different element, you'll need to set the draggable attribute to true.

We’ll start with the HTML that sets up the images for our four vehicles: fire truck, ambulance, car and bicycle.

<ul class="vehicles"> <li> <!-- Fire Truck --> <!-- <code>img<code> elements don't need a <code>draggable<code> attribute like other elements --> <img id="fire-truck" alt="fire truck" src=""/> </li> <li> <!-- Ambulance --> <img id="ambulance" alt="ambulance" src=""> </li> <li> <!-- Car --> <img id="car" alt="car" src=""> </li> <li> <!-- Bike --> <img id="bike" alt="bicycle" src=""> </li> </ul>

Since images are draggable by default, you’ll see dragging any one of them creates a ghost image.

Just adding a draggable attribute to an element that’s not an image or link is really all you need to make an element draggable in most browsers. To make elements draggable in all browsers, you need to define some event handlers. They are also useful for adding extra functionality like a border if an element is being dragged around or a sound if it stops being dragged. For these, you’re going to need some drag event handlers, so let’s look at those.

Drag Events

There are three drag-related events you can listen for but we’re only going to use two: dragstart and dragend.

  • dragstart - Triggered as soon as we start dragging. This is where we can define the drag data and the drag effect.
  • dragend - Triggered when a draggable element is dropped. This event is generally fired right after the drop zone’s drop event.

We’ll cover what the drag data and the drag effect is shortly.

let dragged; // Keeps track of what's being dragged - we'll use this later! function onDragStart(event) { let target =; if (target && target.nodeName === 'IMG') { // If target is an image dragged = target; event.dataTransfer.setData('text',; event.dataTransfer.dropEffect = 'move'; // Make it half transparent when it's being dragged = .3; } } function onDragEnd(event) { if ( && === 'IMG') { // Reset the transparency = ''; // Reset opacity when dragging ends dragged = null; } } // Adding event listeners const vehicles = document.querySelector('.vehicles'); vehicles.addEventListener('dragstart', onDragStart); vehicles.addEventListener('dragend', onDragEnd);

There are a couple of things happening in this code:

  • We are defining the drag data. Each drag event has a property called dataTransfer that stores the event's data. You can use the setData(type, data) method to add a dragged item to the drag data. We’re storing the dragged image’s ID as type 'text' in line 7.
  • We’re storing the element being dragged in a global variable. I know, I know. Global is dangerous for scoping but here’s why we do it: although you can store the dragged item using setData, you can’t retrieve it using event.dataTransfer.getData() in all browsers (except Firefox) because the drag data is protected mode. You can read more about it here. I wanted to mention defining the drag data just so you know about it.
  • We’re setting the dropEffect to move. The dropEffect property is used to control the feedback the user is given during a drag and drop operation. For example, it changes which cursor the browser displays while dragging. There are three effects: copy, move and link.
    • copy - Indicates that the data being dragged will be copied from its source to the drop location.
    • move - Indicates that the data being dragged will be moved.
    • link - Indicates that some form of relationship will be created between the source and drop locations.

Now we have draggable vehicles but nowhere to drop them:

See the Pen 1 - Can you park here? by Omayeli Arenyeka (@yelly) on CodePen.


By default, when you drag an element, only form elements such as <input> will be able to accept it as a drop. We’re going to contain our “dropzone” in a <section> element, so we need to add drop event handlers so it can accept drops just like a form element.

First, since it’s an empty element we’re going to need to set a width, height and background color on it so we can see it on screen.

These are the parameters we have available for drop events:

  • dragenter - Triggered at the moment a draggable item enters a droppable area. At least 50% of the draggable element has to be inside the drop zone.
  • dragover - The same as dragenter but it is called repeatedly while the draggable item is within the drop zone.
  • dragleave - Triggered once a draggable item has moved away from a drop zone.
  • drop - Triggered when the draggable item has been released and the drop area agrees to accept the drop.
function onDragOver(event) { // Prevent default to allow drop event.preventDefault(); } function onDragLeave(event) { = ''; } function onDragEnter(event) { const target =; if (target) { event.preventDefault(); // Set the dropEffect to move event.dataTransfer.dropEffect = 'move' = '#1f904e'; } } function onDrop(event) { const target =; if ( target) { = ''; event.preventDefault(); // Get the id of the target and add the moved element to the target's DOM dragged.parentNode.removeChild(dragged); = ''; target.appendChild(dragged); } } const dropZone = document.querySelector('.drop-zone'); dropZone.addEventListener('drop', onDrop); dropZone.addEventListener('dragenter', onDragEnter); dropZone.addEventListener('dragleave', onDragLeave); dropZone.addEventListener('dragover', onDragOver);

If you’re wondering why we keep calling event.preventDefault() it’s because by default the browser assumes any target is not a valid drop target. This isn’t true all the time for all browsers but it’s better to be safe than sorry! Calling preventDefault() on the dragenter, dragover and drop events, informs the browser that the current target is a valid drop target.

Now, we have a simple drag and drop application!

See the Pen 2 - Can you park here? by Omayeli Arenyeka (@yelly) on CodePen.

It’s fun, but not quite as frustrating as parking. We have to create some rules to make that happen.

Rules and Validation

I came up with some random parking rules, and I’d encourage you to create some of your own. Parking signs usually have days and times you can park as well as what types of vehicles are allowed to park at that moment in time. When we were creating our draggable objects, we had four vehicles: an ambulance, a fire truck, a regular car and a bicycle. So, we’re going to create rules for them.

  1. Ambulance parking only: Monday through Friday, 9pm to 3am.
  2. Fire truck parking only: All day during the weekend.
  3. Regular car parking: Monday through Friday, 3am to 3pm.
  4. Bicycle parking: Monday through Friday, 3pm to 9pm.

Now, we translate these rules to code. We’re going to be using two libraries to handle time and ranges: Moment and Moment-range.

The scripts are already available in Codepen to add to any new demo, but if you are developing outside of Codepen you can copy or link them up from here:

<script defer src=""></script> <script defer src=""></script>

Then, we create an object to store all the parking rules.

window['moment-range'].extendMoment(moment); // The array of weekdays const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; const parkingRules = { ambulance: { // The ambulance can only park on weekdays... days: weekdays, // ...from 9pm to 3am (the next day) times: createRange(moment().set('hour', 21), moment().add(1, 'day').set('hour', 3)) }, 'fire truck': { // The fire truck can obnly park on Saturdays and Sundays, but all day days: ['Saturday', 'Sunday'] }, car: { // The car can only park on weekdays... days: weekdays, // ...from 3am - 3pm (the same day) times: createRange(moment().set('hour', 3), moment().set('hour', 15)) }, bicycle: { // The car can only park on weekdays... days: weekdays, // ...from 3pm - 9pm (the same day) times: createRange(moment().set('hour', 15), moment().set('hour', 21)) } }; function createRange(start, end) { if (start && end) { return moment.range(start, end); } }

Each vehicle in the parkingRules object has a days property with an array of days it can park and a times property that is a time range. To get the current time using Moment, call moment(). To create a range using Moment-range, pass a start and end time to the moment.range function.

Now, in the onDragEnter and onDrop event handlers we defined earlier, we add some checks to make sure a vehicle can park. Our alt attribute on the img tag is storing the type of vehicle so we pass that to a canPark method which will return if the car can be parked. We also added visual cues (change in background) to tell the user whether a vehicle can be parked or not.

function onDragEnter(event) { const target =; if (dragged && target) { const vehicleType = dragged.alt; // e.g bicycle, ambulance if (canPark(vehicleType)) { event.preventDefault(); // Set the dropEffect to move event.dataTransfer.dropEffect = 'move'; /* Change color to green to show it can be dropped /* = '#1f904e'; } else { /* Change color to red to show it can't be dropped. Notice we * don't call event.preventDefault() here so the browser won't * allow a drop by default */ = '#d51c00'; } } } function onDrop(event) { const target =; if (target) { const data = event.dataTransfer.getData('text'); const dragged = document.getElementById(data); const vehicleType = dragged.alt; = ''; if (canPark(vehicleType)) { event.preventDefault(); // Get the ID of the target and add the moved element to the target's DOM = ''; target.appendChild(dragged); } } }

Then, we create the canPark method.

function getDay() { return moment().format('dddd'); // format as 'monday' not 1 } function getHours() { return moment().hour(); } function canPark(vehicle) { /* Check the time and the type of vehicle being dragged * to see if it can park at this time */ if (vehicle && parkingRules[vehicle]) { const rules = parkingRules[vehicle]; const validDays = rules.days; const validTimes = rules.times; const curDay = getDay(); if (validDays) { /* If the current day is included on the parking days for the vehicle * And if the current time is within the range */ return validDays.includes(curDay) && (validTimes ? validTimes.contains(moment()) : true); /* Moment.range has a contains function that checks * to see if your range contains a moment. */ } } return false; }

Now, only cars that are allowed to park can park. Lastly, we add the rules to the screen and style it.

Here’s the final result:

See the Pen 3 - Can you park here? by Omayeli Arenyeka (@yelly) on CodePen.

There are lots of ways this could be improved:

  • Auto-generate the HTML for the rules list from the parkingRules object!
  • Add some sound effects!
  • Add ability to drag back vehicles to original point without a page refresh.
  • All those pesky global variables.

But I’ll let you handle that.

If you’re interested in learning more about the DnD API and some critiques of it, here’s some good reading:

The post Creating a Parking Game With the HTML Drag and Drop API appeared first on CSS-Tricks.

?What do you think about headless CMS?

Css Tricks - Thu, 03/08/2018 - 4:14am

(This is a sponsored post.)

Headless CMS is the new kid on the technology block. Some say it’s the only way forward, while others call it a fad without a future. So we decided to conduct a study to see what people think about headless CMS and why they want to use it. Has headless got a future?

Share with us your opinion on the headless CMS and get a chance to win $50 Amazon gift card.

Start the survey

Direct Link to ArticlePermalink

The post ?What do you think about headless CMS? appeared first on CSS-Tricks.

Summer in Paris? Apply now for Type@Paris intensive type design program

Nice Web Type - Wed, 03/07/2018 - 10:52pm

Is your summer schedule still up in the air? Have you always wanted to get into type design? How do you feel about Paris?

Type@Paris is once again running their five-week intensive type design program in the beautiful French capital, where students will spend five days a week in class, go on field trips to study one-of-a-kind collections, learn from industry leaders in public evening lectures, and come away having made their own typeface designs.

Photos by Margaux Saulou, Type@Paris ’17.

Does that sound like where you’d like to be this summer? Apply now! If you’re just coming out of a design program or about to graduate, you may already have portfolio work that qualifies. And no, you do not need to be fluent in French: the Type@Paris program is taught in English.

For those of us who aren’t Paris-bound, we’re especially looking forward to the accompanying evening lecture series, which are free events that will be livestreamed and can be watched from anywhere in the world. We’ll share details about how to watch those later in the summer.

In the meantime, you have until March 14 to apply. Go for it!

What Houdini Means for Animating Transforms

Css Tricks - Wed, 03/07/2018 - 3:15am

I've been playing with CSS transforms for over five years and one thing that has always bugged me was that I couldn't animate the components of a transform chain individually. This article is going to explain the problem, the old workaround, the new magic Houdini solution and, finally, will offer you a feast of eye candy through better looking examples than those used to illustrate concepts.

The Problem

In order to better understand the issue at hand, let's consider the example of a box we move horizontally across the screen. This means one div as far as the HTML goes:

<div class="box"></div>

The CSS is also pretty straightforward. We give this box dimensions, a background and position it in the middle horizontally with a margin.

$d: 4em; .box { margin: .25*$d auto; width: $d; height: $d; background: #f90; }

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, with the help of a translation along the x axis, we move it by half a viewport (50vw) to the left (in the negative direction of the x axis, the positive one being towards the right):

transform: translate(-50vw);

See the Pen by thebabydino (@thebabydino) on CodePen.

Now the left half of the box is outside the screen. Decreasing the absolute amount of translation by half its edge length puts it fully within the viewport while decreasing it by anything more, let's say a full edge length (which is $d or 100%—remember that % values in translate() functions are relative to the dimensions of the element being translated), makes it not even touch the left edge of the viewport anymore.

transform: translate(calc(-1*(50vw - 100%)));

See the Pen by thebabydino (@thebabydino) on CodePen.

This is going to be our initial animation position.

We then create a set of @keyframes to move the box to the symmetrical position with respect to the initial one with no translation and reference them when setting the animation:

$t: 1.5s; .box { /* same styles as before */ animation: move $t ease-in-out infinite alternate; } @keyframes move { to { transform: translate(calc(50vw - 100%)); } }

This all works as expected, giving us a box that moves from left to right and back:

See the Pen by thebabydino (@thebabydino) on CodePen.

But this is a pretty boring animation, so let's make it more interesting. Let's say we want the box to be scaled down to a factor of .1 when it's in the middle and have its normal size at the two ends. We could add one more keyframe:

50% { transform: scale(.1); }

The box now also scales (demo), but, since we've added an extra keyframe, the timing function is not applied for the whole animation anymore—just for the portions in between keyframes. This makes our translation slow in the middle (at 50%) as we now also have a keyframe there. So we need to tweak the timing function, both in the animation value and in the @keyframes. In our case, since we want to have an ease-in-out overall, we can split it into one ease-in and one ease-out.

.box { animation: move $t ease-in infinite alternate; } @keyframes move { 50% { transform: scale(.1); animation-timing-function: ease-out; } to { transform: translate(calc(50vw - 100%)); } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now all works fine, but what if we wanted different timing functions for the translation and scaling? The timing functions we've set mean the animation is slower at the beginning, faster in the middle and then slower again at the end. What if we wanted this to apply just to the translation, but not to the scale? What if we wanted the scaling to happen fast at the beginning, when it goes from 1 towards .1, slow in the middle when it's around .1 and then fast again at the end when it goes back to 1?

The animation timeline (live).

Well, it's just not possible to set different timing functions for different transform functions in the same chain. We cannot make the translation slow and the scaling fast at the beginning or the other way around in the middle. At least, not while what we animate is the transform property and they're part of the same transform chain.

The Old Workaround

There are of course ways of going around this issue. Traditionally, the solution has been to split the transform (and consequently, the animation) over multiple elements. This gives us the following structure:

<div class="wrap"> <div class="box"></div> </div>

We move the width property on the wrapper. Since div elements are block elements by default, this will also determine the width of its .box child without us having to set it explicitly. We keep the height on the .box however, as the height of a child (the .box in this case) also determines the height of its parent (the wrapper in this case).

We also move up the margin, transform and animation properties. In addition to this, we switch back to an ease-in-out timing function for this animation. We also modify the move set of @keyframes to what it was initially, so that we get rid of the scale().

.wrap { margin: .25*$d calc(50% - #{.5*$d}); width: $d; transform: translate(calc(-1*(50vw - 100%))); animation: move $t ease-in-out infinite alternate; } @keyframes move { to { transform: translate(calc(50vw - 100%)); } }

We create another set of @keyframes which we use for the actual .box element. This is an alternating animation of half the duration of the one producing the oscillatory motion.

.box { height: $d; background: #f90; animation: size .5*$t ease-out infinite alternate; } @keyframes size { to { transform: scale(.1); } }

We now have the result we wanted:

See the Pen by thebabydino (@thebabydino) on CodePen.

This is a solid workaround that doesn't add too much extra code, not to mention the fact that, in this particular case, we don't really need two elements, we could do with just one and one of its pseudo-elements. But if our transform chain gets longer, we have no choice but to add extra elements. And, in 2018, we can do better than that!

The Houdini Solution

Some of you may already know that CSS variables are not animatable (and I guess anyone who didn't just found out). If we try to use them in an animation, they just flip from one value to the other when half the time in between has elapsed.

Consider the initial example of the oscillating box (no scaling involved). Let's say we try to animate it using a custom property --x:

.box { /* same styles as before */ transform: translate(var(--x, calc(-1*(50vw - #{$d})))); animation: move $t ease-in-out infinite alternate } @keyframes move { to { --x: calc(50vw - #{$d}) } }

Sadly, this just results in a flip at 50%, the official reason being that browsers cannot know the type of the custom property (which doesn't make sense to me, but I guess that doesn't really matter).

See the Pen by thebabydino (@thebabydino) on CodePen.

But we can forget about all of this because now Houdini has entered the picture and we can register such custom properties so that we explicitly give them a type (the syntax).

For more info on this, check out the talk and slides by Serg Hospodarets.

CSS.registerProperty({ name: '--x', syntax: '<length>', initialValue: 0 });

We've set the initialValue to 0, because we have to set it to something and that something has to be a computationally independent value—that is, it cannot depend on anything we can set or change in the CSS and, given the initial and final translation values depend on the box dimensions, which we set in the CSS, calc(-1*(50vw - 100%)) is not valid here. It doesn't even work to set --x to calc(-1*(50vw - 100%)), we need to use calc(-1*(50vw - #{$d})) instead.

$d: 4em; $t: 1.5s; .box { margin: .25*$d auto; width: $d; height: $d; --x: calc(-1*(50vw - #{$d})); transform: translate(var(--x)); background: #f90; animation: move $t ease-in-out infinite alternate; } @keyframes move { to { --x: calc(50vw - #{$d}); } } The simple oscillating box we get using the new method (live demo, needs Houdini support).

For now, this only works in Blink browsers behind the Experimental Web Platform features flag. This can be enabled from chrome://flags (or, if you're using Opera, opera://flags):

The Experimental Web Platform features flag enabled in Chrome.

In all other browsers, we still see the flip at 50%.

Applying this to our oscillating and scaling demo means we introduce two custom properties we register and animate—one is the translation amount along the x axis (--x) and the other one is the uniform scaling factor (--f).

CSS.registerProperty({ /* same as before */ }); CSS.registerProperty({ name: '--f', syntax: '<number>', initialValue: 1 });

The relevant CSS is as follows:

.box { --x: calc(-1*(50vw - #{$d})); transform: translate(var(--x)) scale(var(--f)); animation: move $t ease-in-out infinite alternate, size .5*$t ease-out infinite alternate; } @keyframes move { to { --x: calc(50vw - #{$d}); } } @keyframes size { to { --f: .1 } } The oscillating and scaling with the new method (live demo, needs Houdini support). Better Looking Stuff

A simple oscillating and scaling square isn't the most exciting thing though, so let's see nicer demos!

More interesting examples. Left: rotating wavy grid of cubes. Right: bouncing square. The 3D version

Going from 2D to 3D, the square becomes a cube and, since just one cube isn't interesting enough, let's have a whole grid of them!

We consider the body to be our scene. In this scene, we have a 3D assembly of cubes (.a3d). These cubes are distributed on a grid of nr rows and nc columns:

- var nr = 13, nc = 13; - var n = nr*nc; .a3d while n-- .cube - var n6hedron= 6; // cube always has 6 faces while n6hedron-- .cube__face

The first thing we do is a few basic styles to create a scene with a perspective, put the whole assembly in the middle and put each cube face into its place. We won't be going into the details of how to build a CSS cube because I've already dedicated a very detailed article to this topic, so if you need a recap, check that one out!

The result so far can be seen below - all the cubes stacked up in the middle of the scene:

All the cubes stacked up in the middle (live demo).

For all these cubes, their front half is in front of the plane of the screen and their back half is behind the plane of the screen. In the plane of the screen, we have a square section of our cube. This square is identical to the ones representing the cube faces.

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we set the column (--i) and row (--j) indices on groups of cubes. Initially, we set both these indices to 0 for all cubes.

.cube { --i: 0; --j: 0; }

Since we have a number of cubes equal to the number of columns (nc) on every row, we then set the row index to 1 for all cubes after the first nc ones. Then, for all cubes after the first 2*nc ones, we set the row index to 2. And so on, until we've covered all nr rows:

style | .cube:nth-child(n + #{1*nc + 1}) { --j: 1 } | .cube:nth-child(n + #{2*nc + 1}) { --j: 2 } //- and so on | .cube:nth-child(n + #{(nr - 1)*nc + 1}) { --j: #{nr - 1} }

We can compact this in a loop:

style - for(var i = 1; i < nr; i++) { | .cube:nth-child(n + #{i*nc + 1}) { --j: #{i} } -}

Afterwards, we move on to setting the column indices. For the columns, we always need to skip a number of cubes equal to nc - 1 before we encounter another cube with the same index. So, for every cube, the nc-th cube after it is going to have the same index and we're going to have nc such groups of cubes.

(We only need to set the index to the last nc - 1, because all cubes have the column index set to 0 initially, so we can skip the first group containing the cubes for which the column index is 0 - no need to set --i again to the same value it already has.)

style | .cube:nth-child(#{nc}n + 2) { --i: 1 } | .cube:nth-child(#{nc}n + 3) { --i: 2 } //- and so on | .cube:nth-child(#{nc}n + #{nc}) { --i: #{nc - 1} }

This, too, can be compacted in a loop:

style - for(var i = 1; i < nc; i++) { | .cube:nth-child(#{nc}n + #{i + 1}) { --i: #{i} } -}

Now that we have all the row and column indices set, we can distribute these cubes on a 2D grid in the plane of the screen using a 2D translate() transform, according to the illustration below, where each cube is represented by its square section in the plane of the screen and the distances are measured in between transform-origin points (which are, by default, at 50% 50% 0, so dead in the middle of the square cube sections from the plane of the screen):

0) and on the first row (of index 0). All items on the second column (of index 1) are offset horizontally by and edge length. All items on the third column (of index 2) are offset horizontally by two edge lengths. In general, all items on the column of index i are offset horizontally by i edge lengths. All items on the last column (of index nc - 1) are offset horizontally by nc - 1 edge lengths. All items on the second row (of index 1) are offset vertically by and edge length. All items on the third row (of index 2) are offset vertically by two edge lengths. In general, all items on the row of index j are offset vertically by j edge lengths. All items on the last row (of index nr - 1) are offset vertically by nr - 1 edge lengths."/>How to create a basic grid starting from the position of the top left item (live). /* $l is the cube edge length */ .cube { /* same as before */ --x: calc(var(--i)*#{$l}); --y: calc(var(--j)*#{$l}); transform: translate(var(--x), var(--y)); }

This gives us a grid, but it's not in the middle of the screen.

The grid, having the midpoint of the top left cube in the middle of the screen (live demo).

Right now, it's the central point of the top left cube that's in the middle of the screen, as highlighted in the demo above. What we want is for the grid to be in the middle, meaning that we need to shift all cubes left and up (in the negative direction of both the x and y axes) by the horizontal and vertical differences between half the grid dimensions (calc(.5*var(--nc)*#{$l}) and calc(.5*var(--nr)*#{$l}), respectively) and the distances between the top left corner of the grid and the midpoint of the top left cube's vertical cross-section in the plane of the screen (these distances are each half the cube edge, or .5*$l).

The difference between the position of the grid midpoint and the top left item midpoint (live).

Subtracting these differences from the previous amounts, our code becomes:

.cube { /* same as before */ --x: calc(var(--i)*#{$l} - (.5*var(--nc)*#{$l} - .5*#{$l})); --y: calc(var(--j)*#{$l} - (.5*var(--nr)*#{$l} - .5*#{$l})); }

Or even better:

.cube { /* same as before */ --x: calc((var(--i) - .5*(var(--nc) - 1))*#{$l})); --y: calc((var(--j) - .5*(var(--nr) - 1))*#{$l})); }

We also need to make sure we set the --nc and --nr custom properties:

- var nr = 13, nc = 13; - var n = nr*nc; //- same as before .a3d(style=`--nc: ${nc}; --nr: ${nr}`) //- same as before

This gives us a grid that's in the middle of the viewport:

The grid is now in the middle (live).

We've also made the cube edge length $l smaller so that the grid fits within the viewport.

Alternatively, we can go for a CSS variable --l instead so that we can control the edge length depending on the number of columns and rows. The first step here is setting the maximum of the two to a --nmax variable:

- var nr = 13, nc = 13; - var n = nr*nc; //- same as before .a3d(style=`--nc: ${nc}; --nr: ${nr}; --max: ${Math.max(nc, nr)}`) //- same as before

Then, we set the edge length (--l) to something like 80% (completely arbitrary value) of the minimum viewport dimension over this maximum (--max):

.cube { /* same as before */ --l: calc(80vmin/var(--max)); }

Finally, we update the cube and face transforms, the face dimensions and margin to use --l instead of $l:

.cube { /* same as before */ --l: calc(80vmin/var(--max)); --x: calc((var(--i) - .5*(var(--nc) - 1))*var(--l)); --y: calc((var(--j) - .5*(var(--nr) - 1))*var(--l)); &__face { /* same as before */ margin: calc(-.5*var(--l)); width: var(--l); height: var(--l); transform: rotate3d(var(--i), var(--j), 0, calc(var(--m, 1)*#{$ba4gon})) translatez(calc(.5*var(--l))); } }

Now we have a nice responsive grid!

The grid is now in the middle and responsive such that it always fits within the viewport (live).

But it's an ugly one, so let's turn it into a pretty rainbow by making the color of each cube depend on its column index (--i):

.cube { /* same as before */ color: hsl(calc(var(--i)*360/var(--nc)), 65%, 65%); } The rainbow grid (live demo).

We've also made the scene background dark so that we have better contrast with the now lighter cube edges.

To spice things up even further, we add a row rotation around the y axis depending on the row index (--j):

.cube { /* same as before */ transform: rotateY(calc(var(--j)*90deg/var(--nr))) translate(var(--x), var(--y)); } The twisted grid (live demo).

We've also decreased the cube edge length --l and increased the perspective value in order to allow this twisted grid to fit in.

Now comes the fun part! For every cube, we animate its position back and forth along the z axis by half the grid width (we make the translate() a translate3d() and use an additional custom property --z that goes between calc(.5*var(--nc)*var(--l)) and calc(-.5*var(--nc)*var(--l))) and its size (via a uniform scale3d() of factor --f that goes between 1 and .1). This is pretty much the same thing we did for the square in our original example, except the motion now happens along the z axis, not along the x axis and the scaling happens in 3D, not just in 2D.

$t: 1s; .cube { /* same as before */ --z: calc(var(--m)*.5*var(--nc)*var(--l)); transform: rotateY(calc(var(--j)*90deg/var(--nr))) translate3d(var(--x), var(--y), var(--z)) scale3d(var(--f), var(--f), var(--f)); animation: a $t ease-in-out infinite alternate; animation-name: move, zoom; animation-duration: $t, .5*$t; } @keyframes move { to { --m: -1 } } @keyframes zoom { to { --f: .1 } }

This doesn't do anything until we register the multiplier --m and the scaling factor --f to give them a type and an initial value:

CSS.registerProperty({ name: '--m', syntax: '<number>', initialValue: 1 }); CSS.registerProperty({ name: '--f', syntax: '<number>', initialValue: 1 }); The animated grid (live demo, needs Houdini support).

At this point, all cubes animate at the same time. To make things more interesting, we add a delay that depends on both the column and row index:

animation-delay: calc((var(--i) + var(--j))*#{-2*$t}/(var(--nc) + var(--nr))); The waving grid effect (live).

The final touch is to add a rotation on the 3D assembly:

.a3d { top: 50%; left: 50%; animation: ry 8s linear infinite; } @keyframes ry { to { transform: rotateY(1turn); } }

We also make the faces opaque by giving them a black background and we have the final result:

The final result (live demo, needs Houdini support).

The performance for this is pretty bad, as it can be seen from the GIF recording above, but it's still interesting to see how far we can push things.

Hopping Square

I came across the original in a comment to another article and, as soon as I saw the code, I thought it was the perfect candidate for a makeover using some Houdini magic!

Let's start by understanding what is happening in the original code.

In the HTML, we have nine divs.

<div class="frame"> <div class="center"> <div class="down"> <div class="up"> <div class="squeeze"> <div class="rotate-in"> <div class="rotate-out"> <div class="square"></div> </div> </div> </div> </div> </div> <div class="shadow"></div> </div> </div>

Now, this animation is a lot more complex than anything I could ever come up with, but, even so, nine elements seems to be overkill. So let's take a look at the CSS, see what they're each used for and see how much we can simplify the code in preparation for switching to the Houdini-powered solution.

Let's start with the animated elements. The .down and .up elements each have an animation related to moving the square vertically:

/* original */ .down { position: relative; animation: down $duration ease-in infinite both; .up { animation: up $duration ease-in-out infinite both; /* the rest */ } } @keyframes down { 0% { transform: translateY(-100px); } 20%, 100% { transform: translateY(0); } } @keyframes up { 0%, 75% { transform: translateY(0); } 100% { transform: translateY(-100px); } }

With @keyframes and animations on both elements having the same duration, we can pull off a make-one-out-of-two trick.

In the case of the first set of @keyframes, all the action (going from -100px to 0) happens in the [0%, 20%] interval, while, in the case of the second one, all the action (going from 0 to -100px) happens in the [75%, 100%] interval. These two intervals don't intersect. Because of this and because both animations have the same duration we can add up the translation values at each keyframe.

  • at 0%, we have -100px from the first set of @keyframes and 0 from the second, which gives us -100px
  • at 20%, we have 0 from the first set of @keyframes and 0 from the second (as we have 0 for any frame from 0% to 75%), which gives us 0
  • at 75%, we have 0 from the first set of @keyframes (as we have 0 for any frame from 20% to 100%) and 0 from the second, which gives us 0
  • at 100%, we have 0 from the first set of @keyframes and -100px from the second, which gives us -100px

Our new code is as follows. We have removed the animation-fill-mode from the shorthand as it doesn't do anything in this case since our animation loops infinitely, has a non-zero duration and no delay:

/* new */ .jump { position: relative; transform: translateY(-100px); animation: jump $duration ease-in infinite; /* the rest */ } @keyframes jump { 20%, 75% { transform: translateY(0); animation-timing-function: ease-in-out; } }

Note that we have different timing functions for the two animations, so we need to switch between them in the @keyframes. We still have the same effect, but we got rid of one element and one set of @keyframes.

Next, we do the same thing for the .rotate-in and .rotate-out elements and their @keyframes:

/* original */ .rotate-in { animation: rotate-in $duration ease-out infinite both; .rotate-out { animation: rotate-out $duration ease-in infinite both; } } @keyframes rotate-in { 0% { transform: rotate(-135deg); } 20%, 100% { transform: rotate(0deg); } } @keyframes rotate-out { 0%, 80% { transform: rotate(0); } 100% { transform: rotate(135deg); } }

In a similar manner to the previous case, we add up the rotation values for each keyframe.

  • at 0%, we have -135deg from the first set of @keyframes and 0deg from the second, which gives us -135deg
  • at 20%, we have 0deg from the first set of @keyframes and 0deg from the second (as we have 0deg for any frame from 0% to 80%), which gives us 0deg
  • at 80%, we have 0deg from the first set of @keyframes (as we have 0deg for any frame from 20% to 100%) and 0deg from the second, which gives us 0deg
  • at 100%, we have 0deg from the first set of @keyframes and 135deg from the second, which gives us 135deg

This means we can compact things to:

/* new */ .rotate { transform: rotate(-135deg); animation: rotate $duration ease-out infinite; } @keyframes rotate { 20%, 80% { transform: rotate(0deg); animation-timing-function: ease-in; } 100% { transform: rotate(135deg); } }

We only have one element with a scaling transform that distorts our white square:

/* original */ .squeeze { transform-origin: 50% 100%; animation: squeeze $duration $easing infinite both; } @keyframes squeeze { 0%, 4% { transform: scale(1); } 45% { transform: scale(1.8, 0.4); } 100% { transform: scale(1); } }

There's not really much we can do here in terms of compacting the code, save for removing the animation-fill-mode and grouping the 100% keyframe with the 0% and 4% ones:

/* new */ .squeeze { transform-origin: 50% 100%; animation: squeeze $duration $easing infinite; } @keyframes squeeze { 0%, 4%, 100% { transform: scale(1); } 45% { transform: scale(1.8, .4); } }

The innermost element (.square) is only used to display the white box and has no transform set on it.

/* original */ .square { width: 100px; height: 100px; background: #fff; }

This means we can get rid of it if we move its styles to its parent element.

/* new */ $d: 6.25em; .rotate { width: $d; height: $d; transform: rotate(-135deg); background: #fff; animation: rotate $duration ease-out infinite; }

We got rid of three elements so far and our structure has become:

.frame .center .jump .squeeze .rotate .shadow

The outermost element (.frame) serves as a scene or container. This is the big blue square.

/* original */ .frame { position: absolute; top: 50%; left: 50%; width: 400px; height: 400px; margin-top: -200px; margin-left: -200px; border-radius: 2px; box-shadow: 1px 2px 10px 0px rgba(0,0,0,0.2); overflow: hidden; background: #3498db; color: #fff; font-family: 'Open Sans', Helvetica, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

There's no text in this demo, so we can get rid of the text-related properties. We can also get rid of the color property since, not only do we not have text anywhere in this demo, but we're also not using this for any borders, shadows, backgrounds (via currentColor) and so on.

We can also avoid taking this containing element out of the document flow by using a flexbox layout on the body. This also eliminates the offsets and the margin properties.

/* new */ $s: 4*$d; body { display: flex; align-items: center; justify-content: center; height: 100vh; } .frame { overflow: hidden; position: relative; width: $s; height: $s; border-radius: 2px; box-shadow: 1px 2px 10px rgba(#000, .2); background: #3498db; }

We've also tied the dimensions of this element to those of the hopping square.

The .center element is only used for positioning its direct children (.jump and .shadow), so we can take it out altogether and use the offsets on it directly on these children.

We use absolute positioning on all .frame descendants. This makes the .jump and .squeeze elements 0x0 boxes, so we tweak the transform-origin for the squeezing transform (100% of 0 is always 0, but the value we want is half the square edge length .5*$d). We also set a margin of minus half the square edge length (-.5*$d) on the .rotate element (to compensate for the translate(-50%, -50%) we had on the removed .center element).

/* new */ .frame * { position: absolute, } .jump { top: $top; left: $left; /* same as before */ } .squeeze { transform-origin: 50% .5*$d; /* same as before */ } .rotate { margin: -.5*$d; /* same as before */ }

Finally, let's take a look at the .shadow element.

/* original */ .shadow { position: absolute; z-index: -1; bottom: -2px; left: -4px; right: -4px; height: 2px; border-radius: 50%; background: rgba(0,0,0,0.2); box-shadow: 0 0 0px 8px rgba(0,0,0,0.2); animation: shadow $duration ease-in-out infinite both; } @keyframes shadow { 0%, 100% { transform: scaleX(.5); } 45%, 50% { transform: scaleX(1.8); } }

We're of course removing the position since we've already set that for all descendants of the .frame. We can also get rid of the z-index if we move the .shadow before the .jump element in the DOM.

Next, we have the offsets. The midpoint of the shadow is offset by $left (just like the .jump element) horizontally and by $top plus half a square edge length (.5*$d) vertically.

We see a height that's set to 2px. Along the other axis, the width computes to the square's edge length ($d) plus 4px from the left and 4px from the right. That's plus 8px in total. But one thing we notice is that the box-shadow with an 8px spread and no blur is just an extension of the background. So we can just increase the dimensions of the our element by twice the spread along both axes and get rid of the box-shadow altogether.

Just like in the case of the other elements, we also get rid of the animation-fill-mode from the animation shorthand:

/* new */ .shadow { margin: .5*($d - $sh-h) (-.5*$sh-w); width: $sh-w; height: $sh-h; border-radius: 50%; transform: scaleX(.5); background: rgba(#000, .2); animation: shadow $duration ease-in-out infinite; } @keyframes shadow { 45%, 50% { transform: scaleX(1.8); } }

We've now reduced the code in the original demo by about 40% while still getting the same result.

See the Pen by thebabydino (@thebabydino) on CodePen.

Our next step is to merge the .jump, .squeeze and rotate components into one, so that we go from three elements to a single one. Just as a reminder, the relevant styles we have at this point are:

.jump { transform: translateY(-100px); animation: jump $duration ease-in infinite; } .squeeze { transform-origin: 50% .5*$d; animation: squeeze $duration $easing infinite; } .rotate { transform: rotate(-135deg); animation: rotate $duration ease-out infinite; } @keyframes jump { 20%, 75% { transform: translateY(0); animation-timing-function: ease-in-out; } } @keyframes squeeze { 0%, 4%, 100% { transform: scale(1); } 45% { transform: scale(1.8, .4); } } @keyframes rotate { 20%, 80% { transform: rotate(0deg); animation-timing-function: ease-in; } 100% { transform: rotate(135deg); } }

The only problem here is that the scaling transform has a transform-origin that's different from the default 50% 50%. Fortunately, we can go around that.

Any transform with a transform-origin different from the default is equivalent to a transform chain with default transform-origin that first translates the element such that its default transform-origin point (the 50% 50% point in the case of HTML elements and the 0 0 point of the viewBox in the case of SVG elements) goes to the desired transform-origin, applies the actual transformation we want (scaling, rotation, shearing, a combination of these... doesn't matter) and then applies the reverse translation (the values for each of the axes of coordinates are multiplied by -1).

Any transform with a transform with a transform-origin different from the default is equivalent to a chain that translates the point of the default transform-origin to that of the custom one, performs the desired transform and then reverses the initial translation (live demo).

Putting this into code means that if we have any transform with transform-origin: $x1 $y1, the following two are equivalent:

/* transform on HTML element with transform-origin != default */ transform-origin: $x1 $y1; transform: var(--transform); /* can be rotation, scaling, shearing */ /* equivalent transform chain on HTML element with default transform-origin */ transform: translate(calc(#{$x1} - 50%), calc(#{$y1} - 50%)) var(--transform) translate(calc(50% - #{$x1}), calc(50% - $y1);

In our particular case, we have the default transform-origin value along the x axis, so we only need to perform a translation along the y axis. By also replacing the hardcoded values with variables, we get the following transform chain:

transform: translateY(var(--y)) translateY(.5*$d) scale(var(--fx), var(--fy)) translateY(-.5*$d) rotate(var(--az));

We can compact this a bit by joining the first two translations:

transform: translateY(calc(var(--y) + #{.5*$d})) scale(var(--fx), var(--fy)) translateY(-.5*$d) rotate(var(--az));

We also put the three animations on the three elements into just one:

animation: jump $duration ease-in infinite, squeeze $duration $easing infinite, rotate $duration ease-out infinite;

And we modify the @keyframes so that we now animate the newly-introduced custom properties --y, --fx, --fy and --az:

@keyframes jump { 20%, 75% { --y: 0; animation-timing-function: ease-in-out; } } @keyframes squeeze { 0%, 4%, 100% { --fx: 1; --fy: 1 } 45% { --fx: 1.8; --fy: .4 } } @keyframes rotate { 20%, 80% { --az: 0deg; animation-timing-function: ease-in; } 100% { --az: 135deg } }

However, this won't work unless we register these CSS variables we have introduced and want to animate:

CSS.registerProperty({ 'name': '--y', 'syntax': '<length>', 'initialValue': '-100px' }); CSS.registerProperty({ 'name': '--fx', 'syntax': '<number>', 'initialValue': 1 }); /* exactly the same for --fy */ CSS.registerProperty({ 'name': '--az', 'syntax': '<angle>', 'initialValue': '-135deg' });

We now have a working demo of the method animating CSS variables. But given that our structure is now one wrapper with two children, we can reduce it further to one element and two pseudo-elements, thus getting the final version which can be seen below. It's worth noting that this only works in Blink browsers with the Experimental Web Platform features flag enabled.

The final result (live, needs Houdini support)

The post What Houdini Means for Animating Transforms appeared first on CSS-Tricks.

Productivity Tip: Time Tracking and Task Lists, Unite!

Css Tricks - Tue, 03/06/2018 - 11:56am

I've shared this little productivity tip with enough folks who have found it useful and figured I'd make a post out of it.

I love time tracking and I love task lists, but boy do I hate managing them both. So, I've been using my time tracker as my task list.

I use Harvest for time tracking. It allows you to create time entries in the future and I suspect many other time tracking apps do the same. That means today I can enter all the time entries I plan on doing tomorrow. Or, if I'm feeling super organized, I can create entries for the following week. All of my tasks are right there in front of me and ready to clock my time.

If I don't get to a task that day? No worries. Harvest has a subtle feature that allows me to move a time entry from one day to another. Now, I'm good to go for the next day.

Again, other apps are probably capable of doing the same.

I know, it's a super small thing but it delights me every day and helps me manage two important things in one.

The post Productivity Tip: Time Tracking and Task Lists, Unite! appeared first on CSS-Tricks.

CSS Techniques and Effects for Knockout Text

Css Tricks - Tue, 03/06/2018 - 5:20am

Knockout text is a technique where words are clipped out of an element and reveal the background. In other words, you only see the background because the letters are knocking out holes. It’s appealing because it opens up typographic styles that we don’t get out of traditional CSS properties, like color.

While we’ve seen a number of ways to accomplish knockout text in the past, there are some modern CSS properties we can use now and even enhance the effect further, like transitions and animations. Let’s see them in action.

Mix Blend Modes

There are four blend modes that effortlessly make text cutouts: multiply, screen, darken, and lighten. Applying these to the top element of a stack of image and text, text being at top, creates the knockout design.

Even though, in most cases, either black or white is used in these blend modes to get a clear distinction between the text and the background, I prefer using a darker or lighter color instead to keep the back image slightly visible, like this:

<div class="backdrop"> <p class="text">Tait?</p> </div> /* Background layer with an image */ .backdrop { background: url("/path/to/image.jpg") center; ... } /* Dark foreground layer, with white text, set to "multiply" mix blend mode */ .text { color: white; background: rgb(59, 2, 6); mix-blend-mode: multiply; ... }

See the Pen CSS Knockout Text by Preethi (@rpsthecoder) on CodePen.

Using a darker (or lighter) color also creates a nice "theme" going on with the image shown through the text.

The multiply blend mode keeps darker colors dark and the lighter colors let through whatever’s behind them: a black portion on the top layer will be fully opaque and white will be fully transparent.

The effects of the multiply mix blend mode.

In the above example, the white text became completely see-through while the darker color around it lets the image behind be seen only a little, as the darker shades remain unaffected.

The screen blend mode reverses the roles: darker colors create translucence while lighter shades remain light and block what’s behind.

The darken and lighten blend modes are similar to multiply and screen, respectively, except that details are lost on the portions of the back image that can be seen. Rather than mixing the shades, the modes choose the darker or the lighter shade of the two layers that are shown.

See below the difference in how the four modes have blended the colors:

/* Knockout text within a dark area */ .multiply { color: white; mix-blend-mode: multiply; background-color: rgb(59, 2, 6); } /* Knockout text within a bright area */ .screen { color: black; mix-blend-mode: screen; background-color: rgb(244, 220, 211); } /* Knockout text within a dark area that's less detailed */ .darken { color: white; mix-blend-mode: darken; background-color: rgb(59, 2, 6); } /* Knockout text within a light area that's less detailed */ .lighten { color: black; mix-blend-mode: lighten; background-color: rgb(244, 220, 211); }

See the Pen CSS Knockout Text by Preethi (@rpsthecoder) on CodePen.

Using a blend mode is the most convenient option to get a knockout text effect, because it allows us to apply additional styles that other techniques might not allow.

Let’s take a closer look at the styles we can use to enhance the knockout effect.

Shadow Blur

Adding a white/black or a light/dark text shadow to the text creates a blurred effect. For example, let’s say I add a text-shadow that's with a large blur radius value:

.text { text-shadow: 0 0 9px white; ... }

Now the edges are less crisp and give a sort of cloudy effect:

See the Pen CSS Blurred Knockout Text by Preethi (@rpsthecoder) on CodePen.


We can even make things move around a little. For example, let’s take build on the text-shadow idea we looked at above and put some movement on it to make it appear that the text is glowing:

.text { animation: glow 3s infinite; ... } @keyframes glow { 0% { text-shadow: 0 0 10px white; } 15% { text-shadow: 2px 2px 10px rgba(255, 255, 255, 1), -2px -2px 10px rgba(255, 255, 255, 1); } 30% { text-shadow: 2px 2px 4px rgba(255, 255, 255, .7), -2px -2px 4px rgba(255, 255, 255, .7); } 50% { text-shadow: 20px 20px 50px rgba(255, 255, 255, .5), -20px -20px 50px rgba(255, 255, 255, .5); } }

See the Pen CSS knockout Text Glow Animation by Preethi (@rpsthecoder) on CodePen.


Transition is another property we can apply to our knockout text and that opens up even more interesting possibilities, like using text-indent on pseudo-classes like :hover.

Here’s how we can use transition on pseudo-classes to introduce a new element into the knockout text:

/* The knockout text */ .text { transition: text-indent .5s; ... } /* On hover, trigger the transition */ .text:hover { text-indent: 5px; transition: text-indent .5s; } /* The thing that slides in on hover */ .text:hover::before { display: inline-block; content: '&#x2708;?'; }

See the Pen CSS Knockout Text Transition by Preethi (@rpsthecoder) on CodePen.

Background Clip

The background-clip CSS property set with the text value clips a background to the shape of its foreground text.

Using background-clip: text <p class="text">Tait?</p> .text { background: url("/path/to/image.jpg") center; background-clip: text; color: transparent; ... }

See the Pen Knockout Text by Preethi (@rpsthecoder) on CodePen.

The transparent text shows the image behind it that’s already cut to the shape of the text. Although this is a true knockout text approach—it literally removes the text’s surrounding background on screen—having no background left to bleed into or move over leaves little space for other effects like blur, or moving text, to take place. That’s where mix-blend-mode has an advantage.

CSS Mask

The first technique we looked at employs masking, a concept where shapes are created on a foreground layer and use color to determine how much of the shape shows the background. The black parts of the foreground hide (or "mask") and white parts reveal the background, or vice-versa. Any gray value between black and white is treated as varying degrees of partial transparency.

CSS mask works the same way: you straight up declare an image to be the mask applied over another image and, depending on the type of the mask, we get a portion cut out. As of writing this post CSS mask is fully supported in Firefox only.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeOperaFirefoxIEEdgeSafari67*52*53NoNoTP*Mobile / TabletiOS SafariOpera MobileOpera MiniAndroidAndroid ChromeAndroid Firefox11.3*37*No62*64*57

Since we are looking specifically into knockout text, the mask needs to be made from text. This is a great use for SVG <mask>, which can create masks from SVG shapes and texts.

<div class="backdrop"> <p class="text"></p> </div> <svg> <defs> <mask id="m"> <rect width="100%" height="100%" fill="white" /> <text x="50%" y="75%" text-anchor="middle">Tait?</text> </mask> </defs> </svg> /* Background layer with an image */ .backdrop { background: url("/path/to/image.jpg") center; ... } /* Dark foreground layer with masking applied */ .text { background-color: rgba(59, 2, 6, 1); mask-type: luminance; /* Referrer to an SVG mask */ mask: url("#m"); ... } svg { width: 75vw; height: 20vw; } /* SVG text inside the mask */ text { font: bolder 12vw 'Alfa Slab One'; }

See the Pen Knockout Text by Preethi (@rpsthecoder) on CodePen.

The luminance value on the mask-type property of the foreground element implements a masking mechanism where parts of this layer, corresponding to the black parts of the mask, become transparent. The parts corresponding to the white portions of the mask remain opaque. The mask property uses the url() value to designate the SVG element used for the mask.

SVG’s <mask> element creates a mask imaged from its contents. The contents I made inside the <mask> are a white rectangle (<rect>) and black text (<text>). The text, being black, brings up the portion of the image behind it to the view after masking.

Blurring, Animation, and Transition

CSS masks open up the same blur and animation effects we were able to use with mix-blend-mode.

That same glowing text from we used before applies here as well, this time applied directly to the <text> element of the SVG:

text { font: bolder 12vw 'Alfa Slab One'; animation: glow 3s infinite; } @keyframes glow { 0% { text-shadow: 0 0 10px white; } 15% { text-shadow: 2px 2px 10px rgba(255, 255, 255, 1), -2px -2px 10px rgba(255, 255, 255, 1); } 30% { text-shadow: 2px 2px 4px rgba(255, 255, 255, .7), -2px -2px 4px rgba(255, 255, 255, .7); } 50% { text-shadow: 20px 20px 50px rgba(255, 255, 255, .5), -20px -20px 50px rgba(255, 255, 255, .5); } }

However, unlike mix-blend-mode, not all the same properties can be animated. For example, text-indent won’t work here and neither will transform. It’s true that CSS transforms can be applied to SVG elements but because our mask is actually being used as a mask in its truest form, browsers might not apply those transformations.

We can always inject a transform SVG attribute using JavaScript, delivering those transformations to the elements inside the mask:

t = document.querySelector('text'); b = document.querySelector('.backdrop'); b.onmouseover = ()=>{ t.setAttribute('transform', 'translate(20)'); } b.onmouseout = ()=>{ t.removeAttribute('transform'); } Conclusion

When it comes to browser support and production safe code, CSS mask lags behind due to being limited to Firefox support. The blend modes mentioned in this post are supported in almost all the major browsers, except Edge. The background-clip property is also supported by all the browsers, but still requires -webkit prefix.

In terms of result, both blend modes and mask give similar output. Between background-clip and mix-blend-mode values, it’ll be the choice of the design that’ll lead to choosing one over another. What you can achieve with background-clip can also be done by blending, provided you’re using either only black or white background matching the page’s body.

TLDR; knockout text method uno: apply one of the four knockout-friendly blend modes to the top layer of a text-image stack and use dark/light (or black/white) color combination on the text and its enclosure. Method dos: set background-clip to text in the element carrying both a background image and a transparent text. Method tres: use CSS masking on a solid foreground with an image behind and dictate the cutout using an SVG text mask.

The post CSS Techniques and Effects for Knockout Text appeared first on CSS-Tricks.

Syndicate content
©2003 - Present Akamai Design & Development.