Tag: Custom

Using Custom Property “Stacks” to Tame the Cascade

Since the inception of CSS in 1994, the cascade and inheritance have defined how we design on the web. Both are powerful features but, as authors, we’ve had very little control over how they interact. Selector specificity and source order provide some minimal “layering” control, without a lot of nuance — and inheritance requires an unbroken lineage. Now, CSS Custom Properties allow us to manage and control both cascade and inheritance in new ways.

I want to show you how I’ve used Custom Property “stacks” to solve some of the common issues people face in the cascade: from scoped component styles, to more explicit layering of intents.

A quick intro to Custom Properties

The same way browsers have defined new properties using a vendor prefix like -webkit- or -moz-, we can define our own Custom Properties with an “empty” -- prefix. Like variables in Sass or JavaScript, we can use them to name, store, and retrieve values — but like other properties in CSS, they cascade and inherit with the DOM.

/* Define a custom property */ html {   --brand-color: rebeccapurple; }

In order to access those captured values, we use the var() function. It has two parts: first the name of our custom property, and then a fallback in case that property is undefined:

button {   /* use the --brand-color if available, or fall back to deeppink */   background: var(--brand-color, deeppink); }

This is not a support fallback for old browsers. If a browser doesn’t understand custom properties, it will ignore the entire var() declaration. Instead, this is a built-in way of handling undefined variables, similar to a font stack defining fallback font families when one is unavailable. If we don’t provide a fallback, the default is unset.

Building variable “stacks”

This ability to define a fallback is similar to “font stacks” used on the font-family property. If the first family is unavailable, the second will be used, and so on. The var() function only accepts a single fallback, but we can nest var() functions to create custom-property fallback “stacks” of any size:

button {   /* try Consolas, then Menlo, then Monaco, and finally monospace */   font-family: Consolas, Menlo, Monaco, monospace;    /* try --state, then --button-color, then --brand-color, and finally deeppink */   background: var(--state, var(--button-color, var(--brand-color, deeppink))); }

If that nested syntax for stacked properties looks bulky, you can use a pre-processor like Sass to make it more compact.

That single-fallback limitation is required to support fallbacks with a comma inside them — like font stacks or layered background images:

html {   /* The fallback value is "Helvetica, Arial, sans-serif" */   font-family: var(--my-font, Helvetica, Arial, sans-serif); }

Defining “scope”

CSS selectors allow us to drill down into the HTML DOM tree, and style elements anywhere on the page, or elements in a particular nested context.

/* all links */ a { color: slateblue; }  /* only links inside a section */ section a { color: rebeccapurple; }  /* only links inside an article */ article a { color: deeppink; }

That’s useful, but it doesn’t capture the reality of “modular” object-oriented or component-driven styles. We might have multiple articles and asides, nested in various configurations. We need a way to clarify which context, or scope, should take precedence when they overlap.

Proximity scopes

Let’s say we have a .light theme and a .dark theme. We can use those classes on the root <html> element to define a page-wide default, but we can also apply them to specific components, nested in various ways:

Each time we apply one of our color-mode classes, the background and color properties are reset, then inherited by nested headings and paragraphs. In our main context, colors inherit from the .light class, while the nested heading and paragraph inherit from the .dark class. Inheritance is based on direct lineage, so the nearest ancestor with a defined value will take precedence. We call that proximity.

Proximity matters for inheritance, but it has no impact on selectors, which rely on specificity. That becomes a problem if we want to style something inside the dark or light containers.

Here I’ve attempted to define both light and dark button variants. Light mode buttons should be rebeccapurple with white text so they stand out, and dark mode buttons should be plum with black text. We’re selecting the buttons directly based on a light and dark context, but it doesn’t work:

Some of the buttons are in both contexts, with both .light and .dark ancestors. What we want in that case is for the closest theme to take over (inheritance proximity behavior), but what we get instead is the second selector overriding the first (cascade behavior). Since the two selectors have the same specificity, source order determines the winner.

Custom Properties and proximity

What we need here is a way to inherit these properties from the theme, but only apply them to specific children. Custom Properties make that possible! We can define values on the light and dark containers, while only using their inherited values on nested elements, like our buttons.

We’ll start by setting up the buttons to use custom properties, with a fallback “default” value, in case those properties are undefined:

button {   background: var(--btn-color, rebeccapurple);   color: var(--btn-contrast, white); }

Now we can set those values based on context, and they will scope to the appropriate ancestor based on proximity and inheritance:

.dark {   --btn-color: plum;   --btn-contrast: black; }  .light {   --btn-color: rebeccapurple;   --btn-contrast: white; }

As an added bonus, we’re using less code overall, and one unified button definition:

I think of this as creating an API of available parameters for the button component. Sara Soueidan and Lea Verou have both covered this well in recent articles.

Component ownership

Sometimes proximity isn’t enough to define scope. When JavaScript frameworks generate “scoped styles” they are establishing specific object-element ownership. A “tab layout” component owns the tabs themselves, but not the content behind each tab. This is also what the BEM convention attempts to capture in complex .block__element class names.

Nicole Sullivan coined the term “donut scope” to talk about this problem back in 2011. While I’m sure she has more recent thoughts on the issue, the fundamental problem hasn’t changed. Selectors and specificity are great for describing how we build detailed styles over top of broad patterns, but they don’t convey a clear sense of ownership.

We can use custom property stacks to help solve this problem. We’ll start by creating “global” properties on the <html> element that are for our default colors:

html {   --background--global: white;   --color--global: black;   --btn-color--global: rebeccapurple;   --btn-contrast--global: white; }

That default global theme is now available anywhere we want to refer to it. We’ll do that with a data-theme attribute that applies our foreground and background colors. We want the global values to provide a default fallback, but we also want the option to override with a specific theme. That’s where “stacks” come in:

[data-theme] {   /* If there's no component value, use the global value */   background: var(--background--component, var(--background--global));   color: var(--color--component, var(--color--global)); }

Now we can define an inverted component by setting the *--component properties as a reverse of the global properties:

[data-theme='invert'] {   --background--component: var(--color--global);   --color--component: var(--background--global); }

But we don’t want those settings to inherit beyond the donut of ownership, so we reset those values to initial (undefined) on every theme. We’ll want to do this at a lower specificity, or earlier in the source order, so it provides a default that each theme can override:

[data-theme] {   --background--component: initial;   --color--component: initial; }

The initial keyword has a special meaning when used on custom properties, reverting them to a Guaranteed-Invalid state. That means rather than being passed along to set background: initial or color: initial, the custom property becomes undefined, and we fallback to the next value in our stack, the global settings.

We can do the same thing with our buttons, and then make sure to apply data-theme to each component. If no specific theme is given, each component will default to the global theme:

Defining “origins”

The CSS cascade is a series of filtering layers used to determine what value should take precedence when multiple values are defined on the same property. We most often interact with the specificity layers, or the final layering based on source-order — but the first layer of cascade is the “origin” of a style. The origin describes where a style came from — often the browser (defaults), the user (preferences), or the author (that’s us).

By default, author styles override user preferences, which override browser defaults. That changes when anyone applies `!important` to a style, and the origins reverse: browser `!important` styles have the highest origin, then important user preferences, then our author important styles, above all the normal layers. There are a few additional origins, but we won’t go into them here.

When we create custom property “stacks,” we’re building a very similar behavior. If we wanted to represent existing origins as a stack of custom properties, it would look something like this:

.origins-as-custom-properties {   color: var(--browser-important, var(--user-important, var(--author-important, var(--author, var(--user, var(--browser)))))); }

Those layers already exist, so there’s no reason to recreate them. But we’re doing something very similar when we layer our “global” and “component” styles above — creating a “component” origin layer that overrides our “global” layer. That same approach can be used to solve various layering issues in CSS, which can’t always be described by specificity:

  • Override » Component » Theme » Default
  • Theme » Design system or framework
  • State » Type » Default

Let’s look at some buttons again. We’ll need a default button style, a disabled state, and various button “types,” like danger, primary and secondary. We wan’t the disabled state to always override the type variations, but selectors don’t capture that distinction:

But we can define a stack that provides both “type” and “state” layers in the order that we want them prioritized:

button {   background: var(--btn-state, var(--btn-type, var(--btn-default))); }

Now when we set both variables, the state will always take precedence:

I’ve used this technique to create a Cascading Colors framework that allows custom theming based on layering:

  • Pre-defined theme attributes in the HTML
  • User color preferences
  • Light and dark modes
  • Global theme defaults

Mix and match

These approaches can be taken to an extreme, but most day-to-day use-cases can be handled with two or three values in a stack, often using a combination of the techniques above:

  • A variable stack to define the layers
  • Inheritance to set them based on proximity and scope
  • Careful application of the `initial` value to remove nested elements from a scope

We’ve been using these custom property “stacks” on our projects at OddBird. We’re still discovering as we go, but they’ve already been helpful in solving problems that were difficult using only selectors and specificity. With custom properties, we don’t have to fight the cascade or inheritance. We can capture and leverage them, as-intended, with more control over how they should apply in each instance. To me, that’s a big win for CSS — especially when developing style frameworks, tools, and systems.

The post Using Custom Property “Stacks” to Tame the Cascade appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,

Adding a Custom Welcome Guide to the WordPress Block Editor

I am creating a WordPress plugin and there is a slight learning curve when it comes to using it. I’d like to give users a primer on how to use the plugin, but I want to avoid diverting users to documentation on the plugin’s website since that takes them out of the experience.

What would be great is for users to immediately start using the plugin once it’s installed but have access to helpful tips while they are actively using it. There’s no native feature for something like this in WordPress but we can make something because WordPress is super flexible like that.

So here’s the idea. We’re going to bake documentation directly into the plugin and make it easily accessible in the block editor. This way, users get to use the plugin right away while having answers to common questions directly where they’re working. 

My plugin operates through several Custom Post Types (CPT). What we’re going to build is essentially a popup modal that users get when they go to these CPTs. 

The WordPress block editor is built in React, which utilizes components that can be customized to and reused for different situations.  That is the case with what we’re making — let’s call it the <Guide> component — which behaves like a modal, but is composed of several pages that the user can paginate through.

WordPress itself has a <Guide> component that displays a welcome guide when opening the block editor for the first time:

Screenshot showing a modal on top of the WordPress block editor welcoming users to the editor for the first time.
WordPress displays a modal with instructions for using the block editor when a user loads the editor for the first time.

The guide is a container filled with content that’s broken up into individual pages. In other words, it’s pretty much what we want. That means we don’t have to re-invent the wheel with this project; we can reuse this same concept for our own plugin.

Let’s do exactly that. 

What we want to achieve

Before we get to the solution, let’s talk about the end goal.

The design satisfies the requirements of the plugin, which is a GraphQL server for WordPress. The plugin offers a variety of CPTs that are edited through custom blocks which, in turn, are defined through templates. There’s a grand total of two blocks: one called “GraphiQL client” to input the GraphQL query, and one called “Persisted query options” to customize the behavior of the execution.

Since creating a query for GraphQL is not a trivial task, I decided to add the guide component to the editor screen for that CPT. It’s available in the Document settings as a panel called “Welcome Guide.”

Screenshot showing the WordPress editor with the document settings panel open in the right column. a welcome guide tab is highlighted in the settings.

Crack that panel open and the user gets a link. That link is what will trigger the modal.

Close-up screenshot of the welcome guide tab opened, revealing a link that says "Open Guide: Creating Persisted Queries."

For the modal itself, I decided to display a tutorial video on using the CPT on the first page, and then describe in detail all the options available in the CPT on subsequent pages.

Screenshot showing the custom modal open in the block editor and containing an embedded video on how to use the plugin.

I believe this layout is an effective way to show documentation to the user. It is out of the way, but still conveniently close to the action. Sure, we can use a different design or even place the modal trigger somewhere else using a different component instead of repurposing <Guide>, but this is perfectly good.

Planning the implementation

The implementation comprises the following steps:

  1. Scaffolding a new script to register the custom sidebar panel
  2. Displaying the custom sidebar panel on the editor for our Custom Post Type only
  3. Creating the guide
  4. Adding content to the guide

Let’s start!

Step 1: Scaffolding the script

Starting in WordPress 5.4, we can use a component called <PluginDocumentSettingPanel> to add a panel on the editor’s Document settings like this:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost;   const PluginDocumentSettingPanelDemo = () => (   <PluginDocumentSettingPanel     name="custom-panel"     title="Custom Panel"     className="custom-panel"   >     Custom Panel Contents   </PluginDocumentSettingPanel> ); registerPlugin( 'plugin-document-setting-panel-demo', {   render: PluginDocumentSettingPanelDemo,   icon: 'palmtree', } );

If you’re experienced with the block editor and already know how to execute this code, then you can skip ahead. I’ve been coding with the block editor for less than three months, and using React/npm/webpack is a new world for me — this plugin is my first project using them! I’ve found that the docs in the Gutenberg repo are not always adequate for beginners like me, and sometimes the documentation is missing altogether, so I’ve had to dig into the source code to find answers.

When the documentation for the component indicates to use that piece of code above, I don’t know what to do next, because <PluginDocumentSettingPanel> is not a block and I am unable to scaffold a new block or add the code there. Plus, we’re working with JSX, which means we need to have a JavaScript build step to compile the code.

I did, however, find the equivalent ES5 code:

var el = wp.element.createElement; var __ = wp.i18n.__; var registerPlugin = wp.plugins.registerPlugin; var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; 
 function MyDocumentSettingPlugin() {   return el(     PluginDocumentSettingPanel,     {       className: 'my-document-setting-plugin',       title: 'My Panel',     },     __( 'My Document Setting Panel' )   ); } 
 registerPlugin( 'my-document-setting-plugin', {   render: MyDocumentSettingPlugin } );

ES5 code does not need be compiled, so we can load it like any other script in WordPress. But I don’t want to use that. I want the full, modern experience of ESNext and JSX.

So my thinking goes like this: I can’t use the block scaffolding tools since it’s not a block, and I don’t know how to compile the script (I’m certainly not going to set-up webpack all by myself). That means I’m stuck.

But wait! The only difference between a block and a regular script is just how they are registered in WordPress. A block is registered like this:

wp_register_script($ blockScriptName, $ blockScriptURL, $ dependencies, $ version); register_block_type('my-namespace/my-block', [   'editor_script' => $ blockScriptName, ]);

And a regular script is registered like this:

wp_register_script($ scriptName, $ scriptURL, $ dependencies, $ version); wp_enqueue_script($ scriptName);

We can use any of the block scaffolding tools to modify things then register a regular script instead of a block, which gains us access to the webpack configuration to compile the ESNext code. Those available tools are:

I chose to use the @wordpress/create-block package because it is maintained by the team developing Gutenberg.

To scaffold the block, we execute this in the command line:

npm init @wordpress/block

After completing all the prompts for information — including the block’s name, title and description — the tool will generate a single-block plugin, with an entry PHP file containing code similar to this:

/**  * Registers all block assets so that they can be enqueued through the block editor  * in the corresponding context.  *  * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/  */ function my_namespace_my_block_block_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-namespace/my-block" block first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-namespace-my-block-block-editor',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   ); 
   $ editor_css = 'editor.css';   wp_register_style(     'my-namespace-my-block-block-editor',     plugins_url( $ editor_css, __FILE__ ),     array(),     filemtime( "$ dir/$ editor_css" )   ); 
   $ style_css = 'style.css';   wp_register_style(     'my-namespace-my-block-block',     plugins_url( $ style_css, __FILE__ ),     array(),     filemtime( "$ dir/$ style_css" )   ); 
   register_block_type( 'my-namespace/my-block', array(     'editor_script' => 'my-namespace-my-block-block-editor',     'editor_style'  => 'my-namespace-my-block-block-editor',     'style'         => 'my-namespace-my-block-block',   ) ); } add_action( 'init', 'my_namespace_my_block_block_init' );

We can copy this code into the plugin, and modify it appropriately, converting the block into a regular script. (Note that I’m also removing the CSS files along the way, but could keep them, if needed.)

function my_script_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-script" script first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-script',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   );   wp_enqueue_script(     'my-script'   ); } add_action( 'init', 'my_script_init' );

Let’s copy the package.json file over:

{   "name": "my-block",   "version": "0.1.0",   "description": "This is my block",   "author": "The WordPress Contributors",   "license": "GPL-2.0-or-later",   "main": "build/index.js",   "scripts": {     "build": "wp-scripts build",     "format:js": "wp-scripts format-js",     "lint:css": "wp-scripts lint-style",     "lint:js": "wp-scripts lint-js",     "start": "wp-scripts start",     "packages-update": "wp-scripts packages-update"   },   "devDependencies": {     "@wordpress/scripts": "^9.1.0"   } }

Now, we can replace the contents of file src/index.js with the ESNext code from above to register the <PluginDocumentSettingPanel> component. Upon running npm start (or npm run build for production) the code will be compiled into build/index.js.

There is a last problem to solve: the <PluginDocumentSettingPanel> component is not statically imported, but instead obtained from wp.editPost, and since wp is a global variable loaded by WordPress on runtime, this dependency is not present in index.asset.php (which is auto-generated during build). We must manually add a dependency to the wp-edit-post script when registering the script to make sure it loads before ours:

$ dependencies = array_merge(   $ script_asset['dependencies'],   [     'wp-edit-post',   ] ); wp_register_script(   'my-script',   plugins_url( $ index_js, __FILE__ ),   $ dependencies,   $ script_asset['version'] );

Now the script setup is ready!

The plugin can be updated with Gutenberg’s relentless development cycles. Run npm run packages-update to update the npm dependencies (and, consequently, the webpack configuration, which is defined on package "@wordpress/scripts") to their latest supported versions.

At this point, you might be wondering how I knew to add a dependency to the "wp-edit-post" script before our script. Well, I had to dig into Gutenberg’s source code. The documentation for <PluginDocumentSettingPanel> is somewhat incomplete, which is a perfect example of how Gutenberg’s documentation is lacking in certain places.

While digging in code and browsing documentation, I discovered a few enlightening things. For example, there are two ways to code our scripts: using either the ES5 or the ESNext syntax. ES5 doesn’t require a build process, and it references instances of code from the runtime environment, most likely through the global wp variable. For instance, the code to create an icon goes like this:

var moreIcon = wp.element.createElement( 'svg' );

ESNext relies on webpack to resolve all dependencies, which enables us to import static components. For instance, the code to create an icon would be:

import { more } from '@wordpress/icons';

This applies pretty much everywhere. However, that’s not the case for the <PluginDocumentSettingPanel> component, which references the runtime environment for ESNext:

const { PluginDocumentSettingPanel } = wp.editPost;

That’s why we have to add a dependency to the “wp-edit-post” script. That’s where the wp.editPost variable is defined.

If <PluginDocumentSettingPanel> could be directly imported, then the dependency to “wp-edit-post” would be automatically handled by the block editor through the Dependency Extraction Webpack Plugin. This plugin builds the bridge from static to runtime by creating a index.asset.php file containing all the dependencies for the runtime environment scripts, which are obtained by replacing "@wordpress/" from the package name with "wp-". Hence, the "@wordpress/edit-post" package  becomes the "wp-edit-post" runtime script. That’s how I figured out which script to add the dependency.

Step 2: Blacklisting the custom sidebar panel on all other CPTs 

The panel will display documentation for a specific CPT, so it must be registered only to that CPT. That means we need to blacklist it from appearing on any other post types.

Ryan Welcher (who created the <PluginDocumentSettingPanel> component) describes this process when registering the panel:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost const { withSelect } = wp.data; 
 const MyCustomSideBarPanel = ( { postType } ) => { 
   if ( 'post-type-name' !== postType ) {     return null;   } 
   return(     <PluginDocumentSettingPanel       name="my-custom-panel"       title="My Custom Panel"     >       Hello, World!     </PluginDocumentSettingPanel>   ); } 
 const CustomSideBarPanelwithSelect = withSelect( select => {   return {     postType: select( 'core/editor' ).getCurrentPostType(),   }; } )( MyCustomSideBarPanel); 
 
 registerPlugin( 'my-custom-panel', { render: CustomSideBarPanelwithSelect } );

He also suggests an alternative solution, using useSelect instead of withSelect.

That said, I’m not totally convinced by this solution, because the JavaScript file must still be loaded, even if it isn’t needed, forcing the website to take a performance hit. Doesn’t it make more sense to not register the JavaScript file than it does to run JavaScript just to disable JavaScript?

I have created a PHP solution. I’ll admit that it feels a bit hacky, but it works well. First, we find out which post type is related to the object being created or edited:

function get_editing_post_type(): ?string {   if (!is_admin()) {     return null;   } 
   global $ pagenow;   $ typenow = '';   if ( 'post-new.php' === $ pagenow ) {     if ( isset( $ _REQUEST['post_type'] ) && post_type_exists( $ _REQUEST['post_type'] ) ) {       $ typenow = $ _REQUEST['post_type'];     };   } elseif ( 'post.php' === $ pagenow ) {     if ( isset( $ _GET['post'] ) && isset( $ _POST['post_ID'] ) && (int) $ _GET['post'] !== (int) $ _POST['post_ID'] ) {       // Do nothing     } elseif ( isset( $ _GET['post'] ) ) {       $ post_id = (int) $ _GET['post'];     } elseif ( isset( $ _POST['post_ID'] ) ) {       $ post_id = (int) $ _POST['post_ID'];     }     if ( $ post_id ) {       $ post = get_post( $ post_id );       $ typenow = $ post->post_type;     }   }   return $ typenow; }

Then, ,we register the script only if it matches our CPT:

add_action('init', 'maybe_register_script'); function maybe_register_script() {   // Check if this is the intended custom post type   if (get_editing_post_type() != 'my-custom-post-type') {     return;   } 
   // Only then register the block   wp_register_script(...);   wp_enqueue_script(...); }

Check out this post for a deeper dive on how this works.

Step 3: Creating the custom guide

I designed the functionality for my plugin’s guide based on the WordPress <Guide> component. I didn’t realize I’d be doing that at first, so here’s how I was able to figure that out.

  1. Search the source code to see how it was done there.
  2. Explore the catalogue of all available components in Gutenberg’s Storybook.

First, I copied content from the block editor modal and did a basic search. The results pointed me to this file. From there I discovered the component is called <Guide> and could simply copy and paste its code to my plugin as a base for my own guide.

Then I looked for the component’s documentation. I browsed the @wordpress/components package (which, as you may have guessed, is where components are implemented) and found the component’s README file. That gave me all the information I needed to implement my own custom guide component.

I also explored the catalogue of all the available components in Gutenberg’s Storybook (which actually shows that these components can be used outside the context of WordPress). Clicking on all of them, I finally discovered <Guide>. The storybook provides the source code for several examples (or stories). It’s a handy resource for understanding how to customize a component through props.

At this point, I knew <Guide> would make a solid base for my component. There is one missing element, though: how to trigger the guide on click. I had to rack my brain for this one!

This is a button with a listener that opens the modal on click:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import MyGuide from './guide'; 
 const MyGuideWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button onClick={ () => setOpen( true ) }>         { __('Open Guide: “Creating Persisted Queries”') }       </Button>       { isOpen && (         <MyGuide            { ...props }           onFinish={ () => setOpen( false ) }         />       ) }     </>   ); }; export default MyGuideWithButton;

Even though the block editor tries to hide it, we are operating within React. Until now, we’ve been dealing with JSX and components. But now we need the useState hook, which is specific to React.

I’d say that having a good grasp of React is required if you want to master the WordPress block editor. There is no way around it.

Step 4: Adding content to the guide

We’re almost there! Let’s create the <Guide> component, containing a <GuidePage> component for each page of content.

The content can use HTML, include other components, and whatnot. In this particular case, I have added three <GuidePage> instances for my CPT just using HTML. The first page includes a video tutorial and the next two pages contain detailed instructions.

import { Guide, GuidePage } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; 
 const MyGuide = ( props ) => {   return (     <Guide { ...props } >       <GuidePage>         <video width="640" height="400" controls>           <source src="https://d1c2lqfn9an7pb.cloudfront.net/presentations/graphql-api/videos/graphql-api-creating-persisted-query.mov" type="video/mp4" />           { __('Your browser does not support the video tag.') }         </video>         // etc.       </GuidePage>       <GuidePage>         // ...       </GuidePage>       <GuidePage>         // ...       </GuidePage>     </Guide>   ) } export default MyGuide;
imaged gif showing the mouse cursor clicking on the Open Guide link in the block editor's document settings, which opens the custom welcome guide containing a video with links to other pages in the modal.
Hey look, we have our own guide now!

Not bad! There are a few issues, though:

  • I couldn’t embed the video inside the <Guide> because clicking the play button closes the guide. I assume that’s because the <iframe> falls outside the boundaries of the guide. I wound up uploading the video file to S3 and serving with <video>.
  • The page transition in the guide is not very smooth. The block editor’s modal looks alright because all pages have a similar height, but the transition in this one is pretty abrupt.
  • The hover effect on buttons could be improved. Hopefully, the Gutenberg team needs to fix this for their own purposes, because my CSS aren’t there. It’s not that my skills are bad; they are nonexistent.

But I can live with these issues. Functionality-wise, I’ve achieved what I need the guide to do.

Bonus: Opening docs independently 

For our <Guide>, we created the content of each <GuidePage> component directly using HTML. However, if this HTML code is instead added through an autonomous component, then it can be reused for other user interactions.

For instance, the component <CacheControlDescription> displays a description concerning HTTP caching:

const CacheControlDescription = () => {   return (     <p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or "no-store" if the max-age is 0</p>   ) } export default CacheControlDescription;

This component can be added inside a <GuidePage> as we did before, but also within a <Modal> component:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import CacheControlDescription from './cache-control-desc'; 
 const CacheControlModalWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button          icon="editor-help"         onClick={ () => setOpen( true ) }       />       { isOpen && (         <Modal            { ...props }           onRequestClose={ () => setOpen( false ) }         >           <CacheControlDescription />         </Modal>       ) }     </>   ); }; export default CacheControlModalWithButton;

To provide a good user experience, we can offer to show the documentation only when the user is interacting with the block. For that, we show or hide the button depending on the value of isSelected:

import { __ } from '@wordpress/i18n'; import CacheControlModalWithButton from './modal-with-btn'; 
 const CacheControlHeader = ( props ) => {   const { isSelected } = props;   return (     <>       { __('Cache-Control max-age') }       { isSelected && (         <CacheControlModalWithButton />       ) }     </>   ); } export default CacheControlHeader;

Finally, the <CacheControlHeader> component is added to the appropriate control.

Animated gif showing the option to view a guide displaying when a block is selected in the editor.

Tadaaaaaaaa 🎉

The WordPress block editor is quite a piece of software! I was able to accomplish things with it that I would have been unable to without it. Providing documentation to the user may not be the shiniest of examples or use cases, but it’s a very practical one and something that’s relevant for many other plugins. Want to use it for your own plugin? Go for it!

The post Adding a Custom Welcome Guide to the WordPress Block Editor appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Striking a Balance Between Native and Custom Select Elements

Here’s the plan! We’re going to build a styled select element. Not just the outside, but the inside too. Total styling control. Plus we’re going to make it accessible. We’re not going to try to replicate everything that the browser does by default with a native <select> element. We’re going to literally use a <select> element when any assistive tech is used. But when a mouse is being used, we’ll show the styled version and make it function as a select element.

That’s what I mean by “hybrid” selects: they are both a native <select> and a styled alternate select in one design pattern.

Custom selects (left) are often used in place of native selects (right) for aesthetics and design consistency.

Select, dropdown, navigation, menu… the name matters

While doing the research for this article, I thought about many names that get tossed around when talking about selects, the most common of which are “dropdown” and “menu.” There are two types of naming mistakes we could make: giving the same name to different things, or giving different names to the same thing. A select can suffer from both mistakes.

Before we move ahead, let me try to add clarity around using “dropdown” as a term. Here’s how I define the meaning of dropdown:

Dropdown: An interactive component that consists of a button that shows and hides a list of items, typically on mouse hover, click or tap. The list is not visible by default until the interaction starts. The list usually displays a block of content (i.e. options) on top of other content.

A lot of interfaces can look like a dropdown. But simply calling an element a “dropdown” is like using “fish” to describe an animal. What type of fish it is? A clownfish is not the same as a shark. The same goes for dropdowns.

Like there are different types of fish in the sea, there are different types of components that we might be talking about when we toss the word “dropdown” around:

  • Menu: A list of commands or actions that the user can perform within the page content.
  • Navigation: A list of links used for navigating through a website.
  • Select: A form control (<select>) that displays a list of options for the user to select within a form.

Deciding what type of dropdown we’re talking about can be a foggy task. Here are some examples from around the web that match how I would classify those three different types. This is based on my research and sometimes, when I can’t find a proper answer, intuition based on my experience.

Dropdown-land: Five scenarios where different dropdowns are used across the internet. Read the table below for a detailed description.
Diagram Label Scenario Dropdown Type
1 The dropdown expects a selected option to be submitted within a form context (e.g. Select Age) Select
2 The dropdown does not need an active option (e.g. A list of actions: copy, paste and cut) Menu
3 The selected option influences the content. (e.g. sorting list) Menu or Select (more about it later)
4 The dropdown contains links to other pages. (e.g. A “meganav” with websites links) Disclosure Navigation
5 The dropdown has content that is not a list. (e.g. a date picker) Something else that should not be called dropdown

Not everyone perceives and interacts with the internet in the same way. Naming user interfaces and defining design patterns is a fundamental process, though one with a lot of room for personal interpretation. All of that variation is what drives the population of dropdown-land. 

There is a dropdown type that is clearly a menu. Its usage is a hot topic in conversations about accessibility. I won’t talk much about it here, but let me just reinforce that the <menu> element is deprecated and no longer recommended. And here’s a detailed explanation about inclusive menus and menus buttons, including why ARIA menu role should not be used for site navigation.

We haven’t even touched on other elements that fall into a rather gray area that makes classifying dropdowns even murkier because of a lack of practical uses cases from the WCAG community.

Uff… that was a lot. Let’s forget about this dropdown-land mess and focus exclusively on the dropdown type that is clearly a <select> element.

Let’s talk about <select>

Styling form controls is an interesting journey. As MDN puts it, there’s the good, the bad, and the ugly. Good is stuff like <form> which is just a block-level element to style. Bad is stuff like checkboxes, which can be done but is somewhat cumbersome. <select> is definitely in ugly terrain.

A lot of articles have been written about it and, even in 2020, it’s still a challenge to create custom selects and some users still prefer the simple native ones

Among developers, the <select> is the most frustrating form control by far, mainly because of its lack of styling support. The UX struggle behind it is so big that we look for other alternatives. Well, I guess the first rule of <select> is similar to ARIA: avoid using it if you can.

I could finish the article right here with “Don’t use <select>, period.” But let’s face reality: a select is still our best solution in a number of circumstances. That might include scenarios where we’re working with a list that contains a lot of options, layouts that are tight on space, or simply a lack of time or budget to design and implement a great custom interactive component from scratch.

Custom <select> requirements

When we make the decision to create a custom select — even if it’s just a “simple” one — these are the requirements we generally have to work with:

  • There is a button that contains the current selected option.
  • Clicking the box toggles the visibility of the options list (also called listbox).
  • Clicking an option in the listbox updates the selected value. The button text changes and the listbox is closed.
  • Clicking outside the component closes the listbox.
  • The trigger contains a small triangle icon pointing downward to indicate there are options.

Something like this:

Some of you may be thinking this works and is good to go. But wait… does it work for everyone?  Not everyone uses a mouse (or touch screen). Plus, a native <select> element comes with more features we get for free and aren’t included in those requirements, such as:

  • The checked option is perceivable for all users regardless of their visual abilities.
  • The component can interact with a keyboard in a predictable way across all browsers (e.g. using arrow keys to navigate, Enter to select, Esc to cancel, etc.).
  • Assistive technologies (e.g. screen readers) announce the element clearly to users, including its role, name and state.
  • The listbox position is adjusted. (i.e. does not get cut off of the screen).
  • The element respects the user’s operating system preferences (e.g high contrast, color scheme, motion, etc.).

This is where the majority of the custom selects fail in some way. Take a look at some of the major UI components libraries. I won’t mention any because the web is ephemeral, but go give it a try. You’ll likely notice that the select component in one framework behaves differently from another. 

Here are additional characteristics to watch for:

  • Is a listbox option immediately activated on focus when navigating with a keyboard?
  • Can you use Enter and/or Space to select an option?
  • Does the Tab key jump go to the next option in the listbox, or jump to the next form control?
  • What happens when you reach the last option in the listbox using arrow keys? Does it simply stay at the last item, does it go back to the first option, or worst of all, does focus move to the next form control? 
  • Is it possible to jump directly to the last item in the listbox using the Page Down key?
  • Is it possible to scroll through the listbox items if there are more than what is currently in view?

This is a small sample of the features included in a native <select> element.

Once we decide to create our own custom select, we are forcing people to use it in a certain way that may not be what they expect.

But it gets worse. Even the native <select> behaves differently across browsers and screen readers. Once we decide to create our own custom select, we are forcing people to use it in a certain way that may not be what they expect. That’s a dangerous decision and it’s in those details where the devil lives.

Building a “hybrid” select

When we build a simple custom select, we are making a trade-off without noticing it. Specifically, we sacrifice functionality to aesthetics. It should be the other way around.

What if we instead deliver a native select by default and replace it with a more aesthetically pleasing one if possible? That’s where the “hybrid” select idea comes into action. It’s “hybrid” because it consists of two selects, showing the appropriate one at the right moment:

  • A native select, visible and accessible by default
  • A custom select, hidden until it’s safe to be interacted with a mouse

Let’s start with markup. First, we’ll add a native <select> with <option> items before the custom selector for this to work. (I’ll explain why in just a bit.)

Any form control must have a descriptive label. We could use <label>, but that would focus the native select when the label is clicked. To prevent that behavior, we’ll use a <span> and connect it to the select using aria-labelledby.

Finally, we need to tell Assistive Technologies to ignore the custom select, using aria-hidden="true". That way, only the native select is announced by them, no matter what.

<span class="selectLabel" id="jobLabel">Main job role</span> <div class="selectWrapper">   <select class="selectNative js-selectNative" aria-labelledby="jobLabel">     <!-- options -->     <option></option>   </select>   <div class="selectCustom js-selectCustom" aria-hidden="true">      <!-- The beautiful custom select -->   </div> </div>

This takes us to styling, where we not only make things look pretty, but where we handle the switch from one select to the other. We need just a few new declarations to make all the magic happen.

First, both native and custom selects must have the same width and height. This ensures people don’t see major differences in the layout when a switch happens.

.selectNative, .selectCustom {   position: relative;   width: 22rem;   height: 4rem; }

There are two selects, but only one can dictate the space that holds them. The other needs to be absolutely positioned to take it out of the document flow. Let’s do that to the custom select because it’s the “replacement” that’s used only if it can be. We’ll hide it by default so it can’t be reached by anyone just yet.

.selectCustom {   position: absolute;   top: 0;   left: 0;   display: none; }

Here comes the “funny” part. We need to detect if someone is using a device where hover is part of the primary input, like a computer with a mouse. While we typically think of media queries for responsive breakpoints or checking feature support, we can use it to detect hover support too using @media query (hover :hover), which is supported by all major browsers. So, let’s use it to show the custom select only on devices that have hover:

@media (hover: hover) {   .selectCustom {     display: block;   } }

Great, but what about people who use a keyboard to navigate even in devices that have hover? What we’ll do is hide the custom select when the native select is in focus. We can reach for an adjacent Sibling combinatioron (+). When the native select is in focus, hide the custom select next to it in the DOM order. (This is why the native select should be placed before the custom one.)

@media (hover: hover) {   .selectNative:focus + .selectCustom {     display: none;   } }

That’s it! The trick to switch between both selects is done! There are other CSS ways to do it, of course, but this works nicely.

Last, we need a sprinkle of JavaScript. Let’s add some event listeners:

  • One for click events that trigger the custom select to open and reveal the options
  • One to sync both selects values. When one select value is changed, the other select value updates as well
  • One for basic keyboard navigation controls, like navigation with Up and Down keys, selecting options with the Enter or Space keys, and closing the select with Esc

Usability testing

I conducted a very small usability test where I asked a few people with disabilities to try the hybrid select component. The following devices and tools were tested using the latest versions of Chrome (81), Firefox (76) and Safari (13):

  • Desktop device using mouse only
  • Desktop device using keyboard only
  • VoiceOver on MacOS using keyboard
  • NVDA on Windows using keyboard
  • VoiceOver on iPhone and iPad using Safari

All these tests worked as expected, but I believe this could have even more usability tests with more diverse people and tools. If you have access to other devices or tools — such as JAWS, Dragon, etc. — please tell me how the test goes.

An issue was found during testing. Specifically, the issue was with the VoiceOver setting “Mouse pointers: Moves Voice Over cursor.” If the user opens the select with a mouse, the custom select will be opened (instead of the native) and the user won’t experience the native select.

What I most like about this approach is how it uses the best of both worlds without compromising the core functionality:

  • Users on mobile and tablets get the native select, which generally offers a better user experience than a custom select, including performance benefits.
  • Keyboard users get to interact with the native select the way they would expect.
  • Assistive Technologies can interact with the native select like normal.
  • Mouse users get to interact with the enhanced custom select.

This approach provides essential native functionality for everyone without the extra huge code effort to implement all the native features.

Don’t get me wrong. This technique is not a one-size-fits-all solution. It may work for simple selects but probably won’t work for cases that involve complex interactions. In those cases, we’d need to use ARIA and JavaScript to complement the gaps and create a truly accessible custom select.

A note about selects that look like menus

Let’s take a look back at the third Dropdown-land scenario. If you recall, it’s  a dropdown that always has a checked option (e.g. sorting some content). I classified it in the gray area, as either a menu or a select. 

Here’s my line of thought: Years ago, this type of dropdown was implemented mostly using a native <select>. Nowadays, it is common to see it implemented from scratch with custom styles (accessible or not). What we end up with is a select element that looks like a menu. 

Three similar dropdowns that always have a selected option.

A <select>  is a type of menu. Both have similar semantics and behavior, especially in a scenario that involves a list of options where one is always checked.  Now, let me mention the WCAG 3.2.2 On Input (Level A) criterion:

Changing the setting of any user interface component should not automatically cause a change of context unless the user has been advised of the behavior before using the component.

Let’s put this in practice. Imagine a sortable list of students. Visually, it may be obvious that sorting is immediate, but that’s not necessarily true for everyone. So, when using <select>, we risk failing the WCAG guideline because the page content changed, and ignificantly re-arranging the content of a page is considered a change of context.

To ensure the criterion success, we must warn the user about the action before they interact with the element, or include a <button> immediately after the select to confirm the change.

<label for="sortStudents">   Sort students   <!-- Warn the user about the change when a confirmation button is not present. -->   <span class="visually-hidden">(Immediate effect upon selection)</span> </label> <select id="sortStudents"> ... </select>

That said, using a <select> or building a custom menu are both good approaches when it comes to simple menus that change the page content. Just remember that your decision will dictate the amount of work required to make the component fully accessible. This is a scenario where the hybrid select approach could be used.

Final words

This whole idea started as an innocent CSS trick but, after all of this research, I was reminded once more that creating unique experiences without compromising accessibility is not an easy task.

Building truly accessible select components (or any kind of dropdown) is harder than it looks. WCAG provides excellent guidance and best practices, but without specific examples and diverse practical uses cases, the guidelines are mostly aspirational. That’s not to mention the fact that ARIA support is tepid and that native <select> elements look and behave differently across browsers.

The “hybrid” select is just another attempt to create a good looking select while getting as many native features as possible. Don’t look at this technique experiment as an excuse to downplay accessibility, but rather as an attempt to serve both worlds. If you have the resources, time and the needed skills, please do it right and make sure to test it with different users before shipping your component to the world.

P.S. Remember to use a proper name when making a “dropdown” component. 😉

The post Striking a Balance Between Native and Custom Select Elements appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

How to Reverse CSS Custom Counters

I needed a numbered list of blog posts to be listed with the last/high first and going down from there. Like this:

5. Post Title 4. Post Title 3. Post Title 2. Post Title 1. Post Title

But the above is just text. I wanted to do this with a semantic <ol> element.

The easy way

This can be done using HTML’ s reversed property on the <ol>:

<ol reversed>   <li>This</li>   <li>List</li>   <li>Will Be</li>   <li>Numbered In</li>   <li>Reverse</li> </ol>

For most people, this would be enough. Job done. 

But I needed custom styles for the counters. 

Let it be known that custom list number styles can be done with the ::marker pseudo-element, but that isn’t yet supported by Chrome (although I hear it’s coming soon).

Because I wanted fully cross-browser compatible custom number styles, I went with custom counters. 

Adding and styling a custom counter

Styling the counters of an ordered list differently from the rest of the list requires disabling the default counters and creating and show our own using CSS Counters. Chris shared a few recipes a while back that are worth checking out.

Assuming we have the following HTML:

<ol class="fancy-numbered">   <li>Item 1</li>   <li>Item 2</li>   <li>Item 3</li> </ol>

…we first need to disable the default counters that come with all ordered lists by setting the CSS property list-style-type to none like so:

ol.fancy-numbered {   list-style-type: none; }

That takes out all the default numbering. Next, we create a counter in CSS to track the number of items in the list.

ol.fancy-numbered {   list-style-type: none;   counter-reset: a; }

This gives us a counter named “a” but it can be called it whatever you like. Let’s display our counter using the ::before pseudo-element on the list item (<li>).

ol.fancy-numbered li::before {   content: counter(a)'.'; }

This will set the content of the pseudo-element to the value of our counter. Right now, that will print 1’s next to your list item.

We need to tell the CSS counter how to increment.

ol.fancy-numbered li::before {   content: counter(a)'.';   counter-increment: a; }

The starting value of “a” is zero, which seems weird, but the default increment is 1, meaning that becomes the actual starting point.  Incrementing up by 1 just happens to be the default, but we can change that as we’ll soon see.

We can now proceed to apply any custom styles we want to the counter, because the counter is just a text pseudo-element that is wide open to styling:

ol.fancy-numbered li::before {   content: counter(a)'.';   counter-increment: a;      position: absolute;      left: 0;      color: blue;      font-size: 4rem; }

For example, here, we’ve made the counter color blue and increased the font size. These are things that we couldn’t do using the default counter.

Reversing custom counters

If we add the reversed property to the <ol> element like we did before, we will observe no effect because we disabled the default numbering. That’s just what this property does.

<ol class="fancy-numbered" reversed>   <li>Item 1</li>   <li>Item 2</li>   <li>Item 3</li> </ol>

The code above has no effect on our custom numbering. It’s still probably a good idea to leave it on there, since our intention is to reverse the list. This keeps things semantically accurate.

To reverse the visual order of our counter-based numbering, we need to know the total number of items in the list and instruct the counter to start from that number and then decrement from there.

ol.fancy-numbered {   counter-reset: a 4;   list-style-type: none; }

Here, we’re setting counter-reset to 4. In other words, we’re telling the browser to start the count at 4 instead of 1. We use 4 instead of 3, again, because the counter() rule is applied to the first item on the list, which is 0. But, in the case where we’re counting backwards, 4 becomes our 0. If we started from 3 and decremented, wind up at 0 instead of 1.

Next, we alter our counter-increment rule to decrease by 1 rather than increase, by making it a negative integer.

ol.fancy-numbered li:before {   content: counter(a)'.';   counter-increment: a -1;   position: absolute;      left: 0;      color: blue;      font-size: 4rem; }

And that’s it! Now the world is your oyster for stuff like step trackers:

Or how about a timeline:

Maybe a business plan?

The post How to Reverse CSS Custom Counters appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

How to Get All Custom Properties on a Page in JavaScript

We can use JavaScript to get the value of a CSS custom property. Robin wrote up a detailed explanation about this in Get a CSS Custom Property Value with JavaScript. To review, let’s say we’ve declared a single custom property on the HTML element:

html {   --color-accent: #00eb9b; }

In JavaScript, we can access the value with getComputedStyle and getPropertyValue:

const colorAccent = getComputedStyle(document.documentElement)   .getPropertyValue('--color-accent'); // #00eb9b

Perfect. Now we have access to our accent color in JavaScript. You know what’s cool? If we change that color in CSS, it updates in JavaScript as well! Handy.

What happens, though, when it’s not just one property we need access to in JavaScript, but a whole bunch of them?

html {   --color-accent: #00eb9b;   --color-accent-secondary: #9db4ff;   --color-accent-tertiary: #f2c0ea;   --color-text: #292929;   --color-divider: #d7d7d7; }

We end up with JavaScript that looks like this:

const colorAccent = getComputedStyle(document.documentElement).getPropertyValue('--color-accent'); // #00eb9b const colorAccentSecondary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-secondary'); // #9db4ff const colorAccentTertiary = getComputedStyle(document.documentElement).getPropertyValue('--color-accent-tertiary'); // #f2c0ea const colorText = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #292929 const colorDivider = getComputedStyle(document.documentElement).getPropertyValue('--color-text'); // #d7d7d7

We’re repeating ourselves a lot. We could shorten each one of these lines by abstracting the common tasks to a function.

const getCSSProp = (element, propName) => getComputedStyle(element).getPropertyValue(propName); const colorAccent = getCSSProp(document.documentElement, '--color-accent'); // #00eb9b // repeat for each custom property...

That helps reduce code repetition, but we still have a less-than-ideal situation. Every time we add a custom property in CSS, we have to write another line of JavaScript to access it. This can and does work fine if we only have a few custom properties. I’ve used this setup on production projects before. But, it’s also possible to automate this.

Let’s walk through the process of automating it by making a working thing.

What are we making?

We’ll make a color palette, which is a common feature in pattern libraries. We’ll generate a grid of color swatches from our CSS custom properties. 

Here’s the complete demo that we’ll build step-by-step.

A preview of our CSS custom property-driven color palette. Showing six cards, one for each color, including the custom property name and hex value in each card.
Here’s what we’re aiming for.

Let’s set the stage. We’ll use an unordered list to display our palette. Each swatch is a <li> element that we’ll render with JavaScript. 

<ul class="colors"></ul>

The CSS for the grid layout isn’t pertinent to the technique in this post, so we won’t look at in detail. It’s available in the CodePen demo.

Now that we have our HTML and CSS in place, we’ll focus on the JavaScript. Here’s an outline of what we’ll do with our code:

  1. Get all stylesheets on a page, both external and internal
  2. Discard any stylesheets hosted on third-party domains
  3. Get all rules for the remaining stylesheets
  4. Discard any rules that aren’t basic style rules
  5. Get the name and value of all CSS properties
  6. Discard non-custom CSS properties
  7. Build HTML to display the color swatches

Let’s get to it.

Step 1: Get all stylesheets on a page

The first thing we need to do is get all external and internal stylesheets on the current page. Stylesheets are available as members of the global document.

document.styleSheets

That returns an array-like object. We want to use array methods, so we’ll convert it to an array. Let’s also put this in a function that we’ll use throughout this post.

const getCSSCustomPropIndex = () => [...document.styleSheets];

When we invoke getCSSCustomPropIndex, we see an array of CSSStyleSheet objects, one for each external and internal stylesheet on the current page.

The output of getCSSCustomPropIndex, an array of CSSStyleSheet objects

Step 2: Discard third-party stylesheets

If our script is running on https://example.com any stylesheet we want to inspect must also be on https://example.com. This is a security feature. From the MDN docs for CSSStyleSheet:

In some browsers, if a stylesheet is loaded from a different domain, accessing cssRules results in SecurityError.

That means that if the current page links to a stylesheet hosted on https://some-cdn.com, we can’t get custom properties — or any styles — from it. The approach we’re taking here only works for stylesheets hosted on the current domain.

CSSStyleSheet objects have an href property. Its value is the full URL to the stylesheet, like https://example.com/styles.css. Internal stylesheets have an href property, but the value will be null.

Let’s write a function that discards third-party stylesheets. We’ll do that by comparing the stylesheet’s href value to the current location.origin.

const isSameDomain = (styleSheet) => {   if (!styleSheet.href) {     return true;   } 
   return styleSheet.href.indexOf(window.location.origin) === 0; };

Now we use isSameDomain as a filter ondocument.styleSheets.

const getCSSCustomPropIndex = () => [...document.styleSheets]   .filter(isSameDomain);

With the third-party stylesheets discarded, we can inspect the contents of those remaining.

Step 3: Get all rules for the remaining stylesheets

Our goal for getCSSCustomPropIndex is to produce an array of arrays. To get there, we’ll use a combination of array methods to loop through, find values we want, and combine them. Let’s take a first step in that direction by producing an array containing every style rule.

const getCSSCustomPropIndex = () => [...document.styleSheets]   .filter(isSameDomain)   .reduce((finalArr, sheet) => finalArr.concat(...sheet.cssRules), []);

We use reduce and concat because we want to produce a flat array where every first-level element is what we’re interested in. In this snippet, we iterate over individual CSSStyleSheet objects. For each one of them, we need its cssRules. From the MDN docs:

The read-only CSSStyleSheet property cssRules returns a live CSSRuleList which provides a real-time, up-to-date list of every CSS rule which comprises the stylesheet. Each item in the list is a CSSRule defining a single rule.

Each CSS rule is the selector, braces, and property declarations. We use the spread operator ...sheet.cssRules to take every rule out of the cssRules object and place it in finalArr. When we log the output of getCSSCustomPropIndex, we get a single-level array of CSSRule objects.

Example output of getCSSCustomPropIndex producing an array of CSSRule objects

This gives us all the CSS rules for all the stylesheets. We want to discard some of those, so let’s move on.

Step 4: Discard any rules that aren’t basic style rules

CSS rules come in different types. CSS specs define each of the types with a constant name and integer. The most common type of rule is the CSSStyleRule. Another type of rule is the CSSMediaRule. We use those to define media queries, like @media (min-width: 400px) {}. Other types include CSSSupportsRule, CSSFontFaceRule, and CSSKeyframesRule. See the Type constants section of the MDN docs for CSSRule for the full list.

We’re only interested in rules where we define custom properties and, for the purposes in this post, we’ll focus on CSSStyleRule. That does leave out the CSSMediaRule rule type where it’s valid to define custom properties. We could use an approach that’s similar to what we’re using to extract custom properties in this demo, but we’ll exclude this specific rule type to limit the scope of the demo.

To narrow our focus to style rules, we’ll write another array filter:

const isStyleRule = (rule) => rule.type === 1;

Every CSSRule has a type property that returns the integer for that type constant. We use isStyleRule to filter sheet.cssRules.

const getCSSCustomPropIndex = () => [...document.styleSheets]   .filter(isSameDomain)   .reduce((finalArr, sheet) => finalArr.concat(     [...sheet.cssRules].filter(isStyleRule)   ), []);

One thing to note is that we are wrapping ...sheet.cssRules in brackets so we can use the array method filter.

Our stylesheet only had CSSStyleRules so the demo results are the same as before. If our stylesheet had media queries or font-face declarations, isStyleRule would discard them.

Step 5: Get the name and value of all properties

Now that we have the rules we want, we can get the properties that make them up. CSSStyleRule objects have a style property that is a CSSStyleDeclaration object. It’s made up of standard CSS properties, like color, font-family, and border-radius, plus custom properties. Let’s add that to our getCSSCustomPropIndex function so that it looks at every rule, building an array of arrays along the way:

const getCSSCustomPropIndex = () => [...document.styleSheets]   .filter(isSameDomain)   .reduce((finalArr, sheet) => finalArr.concat(     [...sheet.cssRules]       .filter(isStyleRule)       .reduce((propValArr, rule) => {         const props = []; /* TODO: more work needed here */         return [...propValArr, ...props];       }, [])   ), []);

If we invoke this now, we get an empty array. We have more work to do, but this lays the foundation. Because we want to end up with an array, we start with an empty array by using the accumulator, which is the second parameter of reduce. In the body of the reduce callback function, we have a placeholder variable, props, where we’ll gather the properties. The return statement combines the array from the previous iteration — the accumulator — with the current props array.

Right now, both are empty arrays. We need to use rule.style to populate props with an array for every property/value in the current rule:

const getCSSCustomPropIndex = () => [...document.styleSheets]   .filter(isSameDomain)   .reduce((finalArr, sheet) => finalArr.concat(     [...sheet.cssRules]       .filter(isStyleRule)       .reduce((propValArr, rule) => {         const props = [...rule.style].map((propName) => [           propName.trim(),           rule.style.getPropertyValue(propName).trim()         ]);         return [...propValArr, ...props];       }, [])   ), []);

rule.style is array-like, so we use the spread operator again to put each member of it into an array that we loop over with map. In the map callback, we return an array with two members. The first member is propName (which includes color, font-family, --color-accent, etc.). The second member is the value of each property. To get that, we use the getPropertyValue method of CSSStyleDeclaration. It takes a single parameter, the string name of the CSS property. 

We use trim on both the name and value to make sure we don’t include any leading or trailing whitespace that sometimes gets left behind.

Now when we invoke getCSSCustomPropIndex, we get an array of arrays. Every child array contains a CSS property name and a value.

Output of getCSSCustomPropIndex showing an array of arrays containing every property name and value

This is what we’re looking for! Well, almost. We’re getting every property in addition to custom properties. We need one more filter to remove those standard properties because all we want are the custom properties.

Step 6: Discard non-custom properties

To determine if a property is custom, we can look at the name. We know custom properties must start with two dashes (--). That’s unique in the CSS world, so we can use that to write a filter function:

([propName]) => propName.indexOf("--") === 0)

Then we use it as a filter on the props array:

const getCSSCustomPropIndex = () =>   [...document.styleSheets].filter(isSameDomain).reduce(     (finalArr, sheet) =>       finalArr.concat(         [...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule) => {           const props = [...rule.style]             .map((propName) => [               propName.trim(),               rule.style.getPropertyValue(propName).trim()             ])             .filter(([propName]) => propName.indexOf("--") === 0); 
           return [...propValArr, ...props];         }, [])       ),     []   );

In the function signature, we have ([propName]). There, we’re using array destructuring to access the first member of every child array in props. From there, we do an indexOf check on the name of the property. If -- is not at the beginning of the prop name, then we don’t include it in the props array.

When we log the result, we have the exact output we’re looking for: An array of arrays for every custom property and its value with no other properties.

The output of getCSSCustomPropIndex showing an array of arrays containing every custom property and its value

Looking more toward the future, creating the property/value map doesn’t have to require so much code. There’s an alternative in the CSS Typed Object Model Level 1 draft that uses CSSStyleRule.styleMap. The styleMap property is an array-like object of every property/value of a CSS rule. We don’t have it yet, but If we did, we could shorten our above code by removing the map:

// ... const props = [...rule.styleMap.entries()].filter(/*same filter*/); // ...

At the time of this writing, Chrome and Edge have implementations of styleMap but no other major browsers do. Because styleMap is in a draft, there’s no guarantee that we’ll actually get it, and there’s no sense using it for this demo. Still, it’s fun to know it’s a future possibility!

We have the data structure we want. Now let’s use the data to display color swatches.

Step 7: Build HTML to display the color swatches

Getting the data into the exact shape we needed was the hard work. We need one more bit of JavaScript to render our beautiful color swatches. Instead of logging the output of getCSSCustomPropIndex, let’s store it in variable.

const cssCustomPropIndex = getCSSCustomPropIndex();

Here’s the HTML we used to create our color swatch at the start of this post:

<ul class="colors"></ul>

We’ll use innerHTML to populate that list with a list item for each color:

document.querySelector(".colors").innerHTML = cssCustomPropIndex.reduce(   (str, [prop, val]) => `$ {str}<li class="color">     <b class="color__swatch" style="--color: $ {val}"></b>     <div class="color__details">       <input value="$ {prop}" readonly />       <input value="$ {val}" readonly />     </div>    </li>`,   "");

We use reduce to iterate over the custom prop index and build a single HTML-looking string for innerHTML. But reduce isn’t the only way to do this. We could use a map and join or forEach. Any method of building the string will work here. This is just my preferred way to do it.

I want to highlight a couple specific bits of code. In the reduce callback signature, we’re using array destructuring again with [prop, val], this time to access both members of each child array. We then use the prop and val variables in the body of the function.

To show the example of each color, we use a b element with an inline style:

<b class="color__swatch" style="--color: $ {val}"></b>

That means we end up with HTML that looks like:

<b class="color__swatch" style="--color: #00eb9b"></b>

But how does that set a background color? In the full CSS we use the custom property --color as the value of  background-color for each .color__swatch. Because external CSS rules inherit from inline styles, --color  is the value we set on the b element.

.color__swatch {   background-color: var(--color);   /* other properties */ }

We now have an HTML display of color swatches representing our CSS custom properties!


This demo focuses on colors, but the technique isn’t limited to custom color props. There’s no reason we couldn’t expand this approach to generate other sections of a pattern library, like fonts, spacing, grid settings, etc. Anything that might be stored as a custom property can be displayed on a page automatically using this technique.

The post How to Get All Custom Properties on a Page in JavaScript appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Global CSS options with custom properties

With a preprocessor, like Sass, building a logical “do this or don’t” setting is fairly straightforward:

$  option: false;  @mixin doThing {   @if $  option {     do-thing: yep;   } }  .el {   @include doThing; }

Can we do that in native CSS with custom properties? Mark Otto shows that we can. It’s just a smidge different.

html {   --component-shadow: 0 .5rem 1rem rgba(0,0,0,.1); }  .component {   box-shadow: var(--component-shadow); }  <!-- override the global anywhere more specific! like      <div class="component remove-shadow">      or      <body class="remove-shadow"> --> .remove-shadow {   --component-shadow: none; }

Direct Link to ArticlePermalink

The post Global CSS options with custom properties appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Working With MDX Custom Elements and Shortcodes

MDX is a killer feature for things like blogs, slide decks and component documentation. It allows you to write Markdown without worrying about HTML elements, their formatting and placement while sprinkling in the magic of custom React components when necessary.

Let’s harness that magic and look at how we can customize MDX by replacing Markdown elements with our own MDX components. In the process, we’ll introduce the concept of “shortcodes” when using those components.

As a heads up, the code snippets here are based on GatsbyJS and React, but MDX can be written with different frameworks as well. If you need a primer on MDX, start here first. This article extends that one with more advanced concepts.

Setting up a layout

We almost always want to render our MDX-based pages in a common layout. That way, they can be arranged with other components on our website. We can specify a default Layout component with the MDX plugin we’re using. For example. we can define a a layout with the gatsby-plugin-mdx plugin like this:

{   resolve: `gatsby-plugin-mdx`,   options: {     defaultLayouts: {       default: path.resolve('./src/templates/blog-post.js'),     },     // ...other options   } }

This would require the src/templates/blog-post.js file to contain a component that would render the children prop it receives.

import { MDXRenderer } from 'gatsby-plugin-mdx'; 
 function BlogPost({ children }) {   return (     <div>{children}</div>   ); } 
 export default BlogPost;

If we are programmatically creating pages, we’d have to use a component named MDXRenderer to achieve the same thing, as specified in the Gatsby docs.

Custom Markdown elements

While MDX is a format where that lets us write custom HTML and React components, its power is rendering Markdown with custom content. But what if we wanted to customize how these Markdown elements render on screen?

We could surely write a remark plugin for it, but MDX provides us with a better, simpler solution. By default, these are some of the elements being rendered by Markdown:

Name HTML Element MDX Syntax
Paragraph <p>
Heading 1 <h1> #
Heading 2 <h2> ##
Heading 3 <h3> ###
Heading 4 <h4> ####
Heading 5 <h5> #####
Heading 6 <h6> ######
Unordered List <ul> -
Ordered List <ol /> 1.
Image <img /> ![alt](https://image-url)
A complete list of components is available in the MDX Docs.

To replace these defaults with our custom React components, MDX ships with a Provider component named  MDXProvider. It relies on the React Context API to inject new custom components and merge them into the defaults provided by MDX.

import React from 'react'; import { MDXProvider } from "@mdx-js/react"; import Image from './image-component'; 
 function Layout({ children }) {   return (     <MDXProvider       components={{         h1: (props) => <h1 {...props} className="text-xl font-light" />         img: Image,       }}      >       {children}     </MDXProvider>   ); } 
 export default Layout;

In this example, any H1 heading (#) in the MDX file will be replaced by the custom implementation specified in the Provider component’s prop while all the other elements will continue to use the defaults. In other words, MDXProvider is able to take our custom markup for a H1 element, merge it with MDX defaults, then apply the custom markup when we write Heading 1 (#) in an MDX file.

MDX and custom components

Customizing MDX elements is great, but what if we want to introduce our own components into the mix?

--- title: Importing Components --- import Playground from './Playground'; 
 Here is a look at the `Playground` component that I have been building: 
 <Playground />

We can import a component into an MDX file and use it the same way we would any React component. And, sure, while this works well for something like a component demo in a blog post, what if we want to use Playground on all blog posts? It would be a pain to import them to all the pages. Instead. MDX presents us with the option to use shortcodes. Here’s how the MDX documentation describes shortcodes:

[A shortcode] allows you to expose components to all of your documents in your app or website. This is a useful feature for common components like YouTube embeds, Twitter cards, or anything else frequently used in your documents.

To include shortcodes in an MDX application, we have to rely on the MDXProvider component again.

import React from 'react'; import { MDXProvider } from "@mdx-js/react"; import Playground from './playground-wrapper'; 
 function Layout({ children }) {   return (     <MDXProvider       components={{         h1: (props) => <h1 {...props} className="text-xl font-light" />         Playground,       }}      >       {children}     </MDXProvider>   ); } 
 export default Layout;

Once we have included custom components into the components object, we can proceed to use them without importing in MDX files.

--- title: Demoing concepts --- 
 Here's the demo for the new concept: 
 <Playground /> 
 > Look ma! No imports

Directly manipulating child components

In React, we get top-level APIs to manipulate children with React.Children. We can use these to pass new props to child components that change their order or determine their visibility. MDX provides us a special wrapper component to access the child components passed in by MDX.

To add a wrapper, we can use the MDXProvider as we did before:

import React from "react"; import { MDXProvider } from "@mdx-js/react"; const components = {   wrapper: ({ children, ...props }) => {     const reversedChildren = React.Children.toArray(children).reverse();     return <>{reversedChildren}</>;   }, }; export default (props) => (   <MDXProvider components={components}>     <main {...props} />   </MDXProvider> );

This example reverses the children so that they appear in reverse order that we wrote it in.

We can even go wild and animate all of MDX children as they come in:

import React from "react"; import { MDXProvider } from "@mdx-js/react"; import { useTrail, animated, config } from "react-spring"; 
 const components = {   wrapper: ({ children, ...props }) => {     const childrenArray = React.Children.toArray(children);     const trail = useTrail(childrenArray.length, {       xy: [0, 0],       opacity: 1,       from: { xy: [30, 50], opacity: 0 },       config: config.gentle,       delay: 200,     });     return (       <section>         {trail.map(({ y, opacity }, index) => (           <animated.div             key={index}             style={{               opacity,               transform: xy.interpolate((x, y) => `translate3d($  {x}px,$  {y}px,0)`),             }}           >             {childrenArray[index]}           </animated.div>         ))}       </section>     );   }, }; 
 export default (props) => (   <MDXProvider components={components}>     <main {...props} />   </MDXProvider> );

Wrapping up

MDX is designed with flexibility out of the box, but extending with a plugin can make it do even more. Here’s what we were just able to do in a short amount of time, thanks to gatsby-plugin-mdx:

  1. Create default Layout components that help format the MDX output.
  2. Replace default HTML elements rendered from Markdown with custom components
  3. Use shortcodes to get rid of us of importing components in every file.
  4. Manipulate children directly to change the MDX output.

Again, this is just another drop in the bucket as far as what MDX does to help make writing content for static sites easier.

More on MDX

The post Working With MDX Custom Elements and Shortcodes appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

How to Create Custom WordPress Editor Blocks in 2020

Peter Tasker on creating blocks right now:

It’s fairly straightforward these days to get set up with the WP CLI ‘scaffold’ command. This command will set up a WordPress theme or plugin with a ‘blocks’ folder that contains the PHP and base CSS and JavaScript required to create a custom block. The only drawback that I noticed is that the JavaScript uses the old ES5 syntax rather than modern ESNext. Modern JavaScript allows us to write more concise code and use JSX in our custom block code.

You can also use the ‘create-guten-block’ tool by Ahmad Awais. It gives you a lot of the boilerplate stuff you need out of the box, like Webpack, ESNext support etc. Setting it up is fairly straightforward, and it’s similar to Create React App.

I’ve used create-guten-block for the handful of custom blocks I’ve made so far, and have found it a pretty nice experience.

But… I feel like I just sort of lucked into being comfortable with all this. I have one foot in WordPress development and just so happen to have one foot in React development. Building blocks with both technologies together feels decently natural to me. If blocks were Angular or something, I feel like I might not have even given it a shot.

I’ll echo this sentiment:

I also found it really annoying working on a block that’s actively changing in code. Every time you reload Gutenberg, you’ll get the “This block appears to have been modified externally…” message because the markup of the block has changed.

I get why it’s throwing the error, but it slows you down.

At the end, Peter mentions the approach of building blocks that Advanced Custom Fields has. It almost feels like a weird bizarro-reverso world. The ACF approach seems more like what WordPress would have done in a normal world (building blocks with just PHP and templating) and third-parties would be the ones adding all the fancy React stuff.

Direct Link to ArticlePermalink

The post How to Create Custom WordPress Editor Blocks in 2020 appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Creating Color Themes With Custom Properties, HSL, and a Little calc()

Before the advent of CSS custom properties (we might call them “variables” in this article as that’s the spirit of them), implementing multiple color schemes on the same website usually meant writing separate stylesheets. Definitely not the most maintainable thing in the world. Nowadays, though, we can define variables in a single stylesheet and let CSS do the magic.

Even if you aren’t offering something like user-generated or user-chosen color themes, you might still use the concept of theming on your website. For example, it is fairly common to use different colors themes across different areas of the site.

We’re going to build out an example like this:

Same layout, different colors.

In this example, all that changes between sections is the color hue; the variations in lightness are always the same. Here’s an example of a simplified color palette for a specific hue:

A palette of multiple hues might look something like this:

This would take effort to do with RGB color value, but in HSL only one value changes.

Enter custom properties

Custom properties have been around for a while and are widely supported. Polyfills and other solutions for IE 11 are also available.

The syntax is very similar to traditional CSS. Here is an overview of the basic usage:

It’s common to see variables defined on the :root pseudo-element, which is always <html> in HTML, but with higher specificity. That said, variables can be defined on any element which is useful for scoping specific variables to specific elements. For example, here are variables defined on data attributes:

Adding calc() to the mix

Variables don’t have to be fixed values. We can leverage the power of the calc() function to automatically calculate values for us while adhering to a uniform pattern:

Since CSS doesn’t support loops, a preprocessor would be handy to generate a part of the code. But remember: CSS variables are not the same as Sass variables.

Implementing CSS variables

What we’re basically trying to do is change the color of the same component on different sections of the same page. Like this:

We have three sections in tabs with their own IDs: #food, #lifestyle, and #travel. Each section corresponds to a different hue. The  data-theme-attribute on the div.wrapper element defines which hue is currently in use.

When #travel is the active tab, we’re using the --first-hue variable, which has a value of 180°. That is what gets used as the --hue value on the section, resulting in a teal color:

<div class="wrapper" data-theme="travel">
.wrapper[data-theme="travel"] {   --hue: var(--first-hue);  /* = 180° = teal */ }

Clicking any of the tabs updates the data-theme attribute to the ID of the section, while removing the hash (#) from it. This takes a smidge of JavaScript. That’s one of the (many) nice things about CSS: they can be accessed and manipulated with JavaScript. This is a far cry from preprocessor variables, which compile into values at the build stage and are no longer accessible in the DOM.

<li><a href="#food">Food</a></li>
const wrapper = document.querySelector('.wrapper'); document.querySelector("nav").addEventListener('click', e => {   e.preventDefault();   e.stopPropagation();   // Get theme name from URL and ditch the hash   wrapper.dataset.theme = e.target.getAttribute('href').substr(1); })

Progressive enhancement

When we use JavaScript, we should be mindful of scenarios where a user may have disabled it. Otherwise, our scripts — and our UI by extension — are inaccessible. This snippet ensures that the site content is still accessible, even in those situations:

document.querySelectorAll('section').forEach((section, i) => {   if (i) { // hide all but the first section     section.style.display = 'none';   } })

This merely allows the tabs to scroll up the page to the corresponding section. Sure, theming is gone, but providing content is much more important.

While I chose to go with a single-page approach, it’s also possible to serve the sections as separate pages and set [data-theme] on the server side. 

Another approach

So far, we’ve assumed that color values change linearly and are thus subject to a mathematical approach. But even in situations where this is only partially true, we may still be able to benefit from the same concept. For instance, if lightness follows a pattern but hue doesn’t, we could split up the stylesheet like this:

<head>   <style>     :root {       --hue: 260;     }   </style>   <link rel="stylesheet" href="stylesheet-with-calculations-based-on-any-hue.css"> </head>

Supporting web components

Web components are an exciting (and evolving) concept. It’s enticing to think we can have encapsulated components that can be reused anywhere and theme them on a case-by-case basis. One component with many contexts!

We can use CSS variable theming with web components. It requires us to use a host-context() pseudo-selector. (Thanks to habemuscode for pointing this out to me!)

:host-context(body[data-theme="color-1"]) {   --shade-1: var(--outsideHSL); }

In summary…

Theming a website with CSS custom properties is much easier than the workaround approaches we’ve resorted to in the past. It’s more maintainable (one stylesheet), performant (less code), and opens up new possibilities (using JavaScript). Not to mention, CSS custom properties become even more powerful when they’re used with HSL colors and the calc() function.

We just looked at one example where we can change the color theme of a component based on the section where it is used. But again, there is much more opportunity here when we start to get into things like letting users change themes themselves – a topic that Chris explores in this article.

The post Creating Color Themes With Custom Properties, HSL, and a Little calc() appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Custom Styling Form Inputs With Modern CSS Features

It’s entirely possible to build custom checkboxes, radio buttons, and toggle switches these days, while staying semantic and accessible. We don’t even need a single line of JavaScript or extra HTML elements! It’s actually gotten easier lately than it has been in the past. Let’s take a look.

Here’s where we’ll end up:

Things sure have gotten easier than they were!

The reason is that we can finally style the ::before and ::after pseudo-elements on the <input> tag itself. This means we can keep and style an <input> and won’t need any extra elements. Before, we had to rely on the likes of an extra <div> or <span>, to pull off a custom design.

Let’s look at the HTML

Nothing special here. We can style our inputs with just this HTML:

<!-- Checkbox --> <input type="checkbox">  <!-- Radio --> <input type="radio">  <!-- Switch --> <input type="checkbox" class="switch">

That’s it for the HTML part, but of course it’s recommended to have name and id attributes, plus a matching <label> element:

<!-- Checkbox --> <input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>  <!-- Radio --> <input type="radio" name="r1" id="r1"> <label for="r1">Radio</label>  <!-- Switch --> <input type="checkbox" class="switch" name="s1" id="s1"> <label for="s1">Switch</label>

Getting into the styling 

First of all, we check for the support of appearance: none;, including it’s prefixed companions. The appearance property is key because it is designed to remove a browser’s default styling from an element. If the property isn’t supported, the styles won’t apply and default input styles will be shown. That’s perfectly fine and a good example of progressive enhancement at play.

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     -webkit-appearance: none;     -moz-appearance: none;   } }

As it stands today, appearance  is a working draft, but here’s what support looks like:

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

Desktop

Chrome Firefox IE Edge Safari
82* 74* No 79* TP*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
79* 68* 76* 13.3*

Like links, we’ve gotta consider different interactive states with form elements. We’ll consider these when styling our elements:

  • :checked
  • :hover
  • :focus
  • :disabled

For example, here’s how we can style our toggle input, create the knob, and account for the :checked state:

/* The toggle container */ .switch {   width: 38px;   border-radius: 11px; }  /* The toggle knob */ .switch::after {   left: 2px;   top: 2px;   border-radius: 50%;   width: 15px;   height: 15px;   background: var(--ab, var(--border));   transform: translateX(var(--x, 0)); }  /* Change color and position when checked */ .switch:checked {   --ab: var(--active-inner);   --x: 17px; }  /* Drop the opacity of the toggle knob when the input is disabled */ .switch:disabled:not(:checked)::after {   opacity: .6; }

We are using the <input> element like a container. The knob inside of the input is created with the ::after pseudo-element. Again, no more need for extra markup!

If you crack open the styles in the demo, you’ll see that we’re defining some CSS custom properties because that’s become such a nice way to manage reusable values in a stylesheet:

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     --active: #275EFE;     --active-inner: #fff;     --focus: 2px rgba(39, 94, 254, .25);     --border: #BBC1E1;     --border-hover: #275EFE;     --background: #fff;     --disabled: #F6F8FF;     --disabled-inner: #E1E6F9;   } }

But there’s another reason we’re using custom properties — they work well for updating values based on the state of the element! We won’t go into full detail here, but here’s an example how we can use custom properties for different states.

/* Default */ input[type='checkbox'], input[type='radio'] {   --active: #275EFE;   --border: #BBC1E1;   border: 1px solid var(--bc, var(--border)); }  /* Override defaults */ input[type='checkbox']:checked, input[type='radio']:checked {   --b: var(--active);   --bc: var(--active); }    /* Apply another border color on hover if not checked & not disabled */ input[type='checkbox']:not(:checked):not(:disabled):hover, input[type='radio']:not(:checked):not(:disabled):hover {   --bc: var(--border-hover); }

For accessibility, we ought to add a custom focus style. We are removing the default outline because it can’t be rounded like the rest of the things we’re styling. But a border-radius along with a box-shadow can make for a rounded style that works just like an outline.

input[type='checkbox'], input[type='radio'] {   --focus: 2px rgba(39, 94, 254, .25);   outline: none;   transition: box-shadow .2s; }  input[type='checkbox']:focus, input[type='radio']:focus {   box-shadow: 0 0 0 var(--focus); }

It’s also possible to align and style the <label> element which directly follows the <input> element in the HTML:

<input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>
input[type='checkbox'] + label, input[type='radio'] + label {   display: inline-block;   vertical-align: top;   /* Additional styling */ }  input[type='checkbox']:disabled + label, input[type='radio']:disabled + label {     cursor: not-allowed; }

Here’s that demo again:

Hopefully, you’re seeing how nice it is to create custom form styles these days. It requires less markup, thanks to pseudo-elements that are directly on form inputs. It requires less fancy style switching, thanks to custom properties. And it has pretty darn good browser support, thanks to @supports.

All in all, this is a much more pleasant developer experience than we’ve had to deal with in the past!

The post Custom Styling Form Inputs With Modern CSS Features appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]