Tag: Native

Dialog Components: Go Native HTML or Roll Your Own?

As the author of a library called AgnosticUI, I’m always on the lookout for new components. And recently, I decided to dig in and start work on a new dialog (aka modal) component. That’s something many devs like to have in their toolset and my goal was to make the best one possible, with an extra special focus on making it inclusive and accessible.

My first thought was that I would avoid any dependencies and bite the bullet to build my own dialog component. As you may know, there’s a new <dialog> element making the rounds and I figured using it as a starting point would be the right thing, especially in the inclusiveness and accessibilities departments.

But, after doing some research, I instead elected to leverage a11y-dialog by Kitty Giraudel. I even wrote adapters so it integrates smoothly with Vue 3, Svelte, and Angular. Kitty has long offered a React adapter as well.

Why did I go that route? Let me take you through my thought process.

First question: Should I even use the native <dialog> element?

The native <dialog> element is being actively improved and will likely be the way forward. But, it still has some issues at the moment that Kitty pointed out quite well:

  1. Clicking the backdrop overlay does not close the dialog by default
  2. The alertdialog ARIA role used for alerts simply does not work with the native <dialog> element. We’re supposed to use that role when a dialog requires a user’s response and shouldn’t be closed by clicking the backdrop, or by pressing ESC.
  3. The <dialog> element comes with a ::backdrop pseudo-element but it is only available when a dialog is programmatically opened with dialog.showModal().

And as Kitty also points out, there are general issues with the element’s default styles, like the fact they are left to the browser and will require JavaScript. So, it’s sort of not 100% HTML anyway.

Here’s a pen demonstrating these points:

Now, some of these issues may not affect you or whatever project you’re working on specifically, and you may even be able to work around things. If you still would like to utilize the native dialog you should see Adam Argyle’s wonderful post on building a dialog component with native dialog.

OK, let’s discuss what actually are the requirements for an accessible dialog component…

What I’m looking for

I know there are lots of ideas about what a dialog component should or should not do. But as far as what I was personally going after for AgnosticUI hinged on what I believe make for an accessible dialog experience:

  1. The dialog should close when clicking outside the dialog (on the backdrop) or when pressing the ESC key.
  2. It should trap focus to prevent tabbing out of the component with a keyboard.
  3. It should allow forwarding tabbing with TAB and backward tabbing with SHIFT+TAB.
  4. It should return focus back to the previously focused element when closed.
  5. It should correctly apply aria-* attributes and toggles.
  6. It should provide Portals (only if we’re using it within a JavaScript framework).
  7. It should support the alertdialog ARIA role for alert situations.
  8. It should prevent the underlying body from scrolling, if needed.
  9. It would be great if our implementation could avoid the common pitfalls that come with the native <dialog> element.
  10. It would ideally provide a way to apply custom styling while also taking the prefers-reduced-motion user preference query as a further accessibility measure.

I’m not the only one with a wish list. You might want to see Scott O’Hara’s article on the topic as well as Kitty’s full write-up on creating an accessible dialog from scratch for more in-depth coverage.

It should be clear right about now why I nixed the native <dialog> element from my component library. I believe in the work going into it, of course, but my current needs simply outweigh the costs of it. That’s why I went with Kitty’s a11y-dialog as my starting point.

Auditing <dialog> accessibility

Before trusting any particular dialog implementation, it’s worth making sure it fits the bill as far as your requirements go. With my requirements so heavily leaning on accessibility, that meant auditing a11y-dialog.

Accessibility audits are a profession of their own. And even if it’s not my everyday primary focus, I know there are some things that are worth doing, like:

This is quite a lot of work, as you might imagine (or know from experience). It’s tempting to take a path of less resistance and try automating things but, in a study conducted by Deque Systems, automated tooling can only catch about 57% of accessibility issues. There’s no substitute for good ol’ fashioned hard work.

The auditing environment

The dialog component can be tested in lots of places, including Storybook, CodePen, CodeSandbox, or whatever. For this particular test, though, I prefer instead to make a skeleton page and test locally. This way I’m preventing myself from having to validate the validators, so to speak. Having to use, say, a Storybook-specific add-on for a11y verification is fine if you’re already using Storybook on your own components, but it adds another layer of complexity when testing the accessibility of an external component.

A skeleton page can verify the dialog with manual checks, existing a11y tooling, and screen readers. If you’re following along, you’ll want to run this page via a local server. There are many ways to do that; one is to use a tool called serve, and npm even provides a nice one-liner npx serve <DIRECTORY> command to fire things up.

Let’s do an example audit together!

I’m obviously bullish on a11y-dialog here, so let’s put it to the test and verify it using some of the the recommended approaches we’ve covered.

Again, all I’m doing here is starting with an HTML. You can use the same one I am (complete with styles and scripts baked right in).

View full code
<!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>A11y Dialog Test</title>     <style>       .dialog-container {         display: flex;         position: fixed;         top: 0;         left: 0;         bottom: 0;         right: 0;         z-index: 2;       }              .dialog-container[aria-hidden='true'] {         display: none;       }              .dialog-overlay {         position: fixed;         top: 0;         left: 0;         bottom: 0;         right: 0;         background-color: rgb(43 46 56 / 0.9);         animation: fade-in 200ms both;       }              .dialog-content {         background-color: rgb(255, 255, 255);         margin: auto;         z-index: 2;         position: relative;         animation: fade-in 400ms 200ms both, slide-up 400ms 200ms both;         padding: 1em;         max-width: 90%;         width: 600px;         border-radius: 2px;       }              @media screen and (min-width: 700px) {         .dialog-content {           padding: 2em;         }       }              @keyframes fade-in {         from {           opacity: 0;         }       }              @keyframes slide-up {         from {           transform: translateY(10%);         }       }        /* Note, for brevity we haven't implemented prefers-reduced-motion */              .dialog h1 {         margin: 0;         font-size: 1.25em;       }              .dialog-close {         position: absolute;         top: 0.5em;         right: 0.5em;         border: 0;         padding: 0;         background-color: transparent;         font-weight: bold;         font-size: 1.25em;         width: 1.2em;         height: 1.2em;         text-align: center;         cursor: pointer;         transition: 0.15s;       }              @media screen and (min-width: 700px) {         .dialog-close {           top: 1em;           right: 1em;         }       }              * {         box-sizing: border-box;       }              body {         font: 125% / 1.5 -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;         padding: 2em 0;       }              h1 {         font-size: 1.6em;         line-height: 1.1;         font-family: 'ESPI Slab', sans-serif;         margin-bottom: 0;       }              main {         max-width: 700px;         margin: 0 auto;         padding: 0 1em;       }     </style>     <script defer src="https://cdn.jsdelivr.net/npm/a11y-dialog@7/dist/a11y-dialog.min.js"></script>   </head>    <body>     <main>       <div class="dialog-container" id="my-dialog" aria-hidden="true" aria-labelledby="my-dialog-title" role="dialog">         <div class="dialog-overlay" data-a11y-dialog-hide></div>         <div class="dialog-content" role="document">           <button data-a11y-dialog-hide class="dialog-close" aria-label="Close this dialog window">             ×           </button>           <a href="https://www.yahoo.com/" target="_blank">Rando Yahoo Link</a>              <h1 id="my-dialog-title">My Title</h1>           <p id="my-dialog-description">             Some description of what's inside this dialog…           </p>         </div>       </div>       <button type="button" data-a11y-dialog-show="my-dialog">         Open the dialog       </button>     </main>     <script>       // We need to ensure our deferred A11yDialog has       // had a chance to do its thing ;-)       window.addEventListener('DOMContentLoaded', (event) => {         const dialogEl = document.getElementById('my-dialog')         const dialog = new A11yDialog(dialogEl)       });     </script>   </body>  </html>

I know, we’re ignoring a bunch of best practices (what, styles in the <head>?!) and combined all of the HTML, CSS, and JavaScript in one file. I won’t go into the details of the code as the focus here is testing for accessibility, but know that this test requires an internet connection as we are importing a11y-dialog from a CDN.

First, the manual checks

I served this one-pager locally and here are my manual check results:

Feature Result
It should close when clicking outside the dialog (on the backdrop) or when pressing the ESC key.
It ought to trap focus to prevent tabbing out of the component with a keyboard.
It should allow forwarding tabbing with TAB and backward tabbing with SHIFT+TAB.
It should return focus back to the previously focused element when closed.
It should correctly apply aria-* attributes and toggles.
I verified this one “by eye” after inspecting the elements in the DevTools Elements panel.
It should provide Portals. Not applicable.
This is only useful when implementing the element with React, Svelte, Vue, etc. We’ve statically placed it on the page with aria-hidden for this test.
It should support for the alertdialog ARIA role for alert situations.
You’ll need to do two things:

First, remove data-a11y-dialog-hide from the overlay in the HTML so that it is <div class="dialog-overlay"></div>. Replace the dialog role with alertdialog so that it becomes:

<div class="dialog-container" id="my-dialog" aria-hidden="true" aria-labelledby="my-dialog-title" aria-describedby="my-dialog-description" role="alertdialog">

Now, clicking on the overlay outside of the dialog box does not close the dialog, as expected.

It should prevent the underlying body from scrolling, if needed.
I didn’t manually test but this, but it is clearly available per the documentation.
It should avoid the common pitfalls that come with the native <dialog> element.
This component does not rely on the native <dialog> which means we’re good here.

Next, let’s use some a11y tooling

I used Lighthouse to test the component both on a desktop computer and a mobile device, in two different scenarios where the dialog is open by default, and closed by default.

a11y-dialog Lighthouse testing, score 100.

I’ve found that sometimes the tooling doesn’t account for DOM elements that are dynamically shown or hidden DOM elements, so this test ensures I’m getting full coverage of both scenarios.

I also tested with IBM Equal Access Accessibility Checker. Generally, this tool will give you a red violation error if there’s anything egregious wrong. It will also ask you to manually review certain items. As seen here, there a couple of items for manual review, but no red violations.

a11y-dialog — tested with IBM Equal Access Accessibility Checker

Moving on to screen readers

Between my manual and tooling checks, I’m already feeling relatively confident that a11y-dialog is an accessible option for my dialog of choice. However, we ought to do our due diligence and consult a screen reader.

VoiceOver is the most convenient screen reader for me since I work on a Mac at the moment, but JAWS and NVDA are big names to look at as well. Like checking for UI consistency across browsers, it’s probably a good idea to test on more than one screen reader if you can.

VoiceOver caption over the a11y-modal example.

Here’s how I conducted the screen reader part of the audit with VoiceOver. Basically, I mapped out what actions needed testing and confirmed each one, like a script:

Step Result
The dialog component’s trigger button is announced. “Entering A11y Dialog Test, web content.”
The dialog should open when pressing CTRL+ALT +Space should show the dialog. “Dialog. Some description of what’s inside this dialog. You are currently on a dialog, inside of web content.”
The dialog should TAB to and put focus on the component’s Close button. “Close this dialog button. You are currently on a button, inside of web content.”
Tab to the link element and confirm it is announced. “Link, Rando Yahoo Link”
Pressing the SPACE key while focused on the Close button should close the dialog component and return to the last item in focus.

Testing with people

If you’re thinking we’re about to move on to testing with real people, I was unfortunately unable to find someone. If I had done this, though, I would have used a similar set of steps for them to run through while I observe, take notes, and ask a few questions about the general experience.

As you can see, a satisfactory audit involves a good deal of time and thought.

Fine, but I want to use a framework’s dialog component

That’s cool! Many frameworks have their own dialog component solution, so there’s lots to choose from. I don’t have some amazing spreadsheet audit of all the frameworks and libraries in the wild, and will spare you the work of evaluating them all.

Instead, here are some resources that might be good starting points and considerations for using a dialog component in some of the most widely used frameworks.

Disclaimer: I have not tested these personally. This is all stuff I found while researching.

Angular dialog options

In 2020, Deque published an article that audits Angular component libraries and the TL;DR was that Material (and its Angular/CDK library) and ngx-bootstrap both appear to provide decent dialog accessibility.

React dialog options

Reakit offers a dialog component that they claim is compliant with WAI-ARIA dialog guidelines, and chakra-ui appears to pay attention to its accessibility. Of course, Material is also available for React, so that’s worth a look as well. I’ve also heard good things about reach/dialog and Adobe’s @react-aria/dialog.

Vue dialog options

I’m a fan of Vuetensils, which is Austin Gil’s naked (aka headless) components library, which just so happens to have a dialog component. There’s also Vuetify, which is a popular Material implementation with a dialog of its own. I’ve also crossed paths with PrimeVue, but was surprised that its dialog component failed to return focus to the original element.

Svelte dialog options

You might want to look at svelte-headlessui. Material has a port in svelterial that is also worth a look. It seems that many current SvelteKit users prefer to build their own component sets as SvelteKit’s packaging idiom makes it super simple to do. If this is you, I would definitely recommend considering svelte-a11y-dialog as a convenient means to build custom dialogs, drawers, bottom sheets, etc.

I’ll also point out that my AgnosticUI library wraps the React, Vue, Svelte and Angular a11y-dialog adapter implementations we’ve been talking about earlier.

Bootstrap, of course

Bootstrap is still something many folks reach for, and unsurprisingly, it offers a dialog component. It requires you to follow some steps in order to make the modal accessible.

If you have other inclusive and accessible library-based dialog components that merit consideration, I’d love to know about them in the comments!

But I’m creating a custom design system

If you’re creating a design system or considering some other roll-your-own dialog approach, you can see just how many things need to be tested and taken into consideration… all for one component! It’s certainly doable to roll your own, of course, but I’d say it’s also extremely prone to error. You might ask yourself whether the effort is worthwhile when there are already battle-tested options to choose from.

I’ll simply leave you with something Scott O’Hara — co-editor of ARIA in HTML and HTML AAM specifications in addition to just being super helpful with all things accessibility — points out:

You could put in the effort to add in those extensions, or you could use a robust plugin like a11y-dialog and ensure that your dialogs will have a pretty consistent experience across all browsers.

Back to my objective…

I need that dialog to support React, Vue, Svelte, and Angular implementations.

I mentioned earlier that a11y-dialog already has ports for Vue and React. But the Vue port hasn’t yet been updated for Vue 3. Well, I was quite happy to spend the time I would have spent creating what likely would have been a buggy hand-rolled dialog component toward helping update the Vue port. I also added a Svelte port and one for Angular too. These are both very new and I would consider them experimental beta software at time of writing. Feedback welcome, of course!

It can support other components, too!

I think it’s worth pointing out that a dialog uses the same underlying concept for hiding and showing that can be used for a drawer (aka off-canvas) component. For example, if we borrow the CSS we used in our dialog accessibility audit and add a few additional classes, then a11y-dialog can be transformed into a working and effective drawer component:

.drawer-start { right: initial; } .drawer-end { left: initial; } .drawer-top { bottom: initial; } .drawer-bottom { top: initial; }  .drawer-content {   margin: initial;   max-width: initial;   width: 25rem;   border-radius: initial; }  .drawer-top .drawer-content, .drawer-bottom .drawer-content {   width: 100%; }

These classes are used in an additive manner, essentially extending the base dialog component. This is exactly what I have started to do as I add my own drawer component to AgnosticUI. Saving time and reusing code FTW!

Wrapping up

Hopefully I’ve given you a good idea of the thinking process that goes into the making and maintenance of a component library. Could I have hand-rolled my own dialog component for the library? Absolutely! But I doubt it would have yielded better results than what a resource like Kitty’s a11y-dialog does, and the effort is daunting. There’s something cool about coming up with your own solution — and there may be good situations where you want to do that — but probably not at the cost of sacrificing something like accessibility.

Anyway, that’s how I arrived at my decision. I learned a lot about the native HTML <dialog> and its accessibility along the way, and I hope my journey gave you some of those nuggets too.


Dialog Components: Go Native HTML or Roll Your Own? originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,

How to Use Native Custom Fields in WordPress (and 5 Useful Examples)

Custom Fields in WordPress are arbitrary bits of data that you can apply to Posts, Pages, and Custom Post Types in WordPress. Metadata, as it were, in the form of key/value pairs. For example:

  • Key: subtitle / Value: They are more than they are cracked up to be
  • Key: header_color_override / Value: #e52e05
  • Key: property_url / Value: https://example.com/123

WordPress has their own documentation of this feature, so I’m not trying to replicate that. I’d just like to show you essentially what custom fields in WordPress are, how they work, how to use them, and some use cases from my own personal experience.

Table of Contents

How to Add/Edit/Remove Custom Fields in WordPress

The UI for Custom Fields in WordPress looks like this:

Showing that Custom Fields in WordPress appear below the content area of the block editor in the admin user interface.

If you don’t see it, it’s possible you may need to go under the three-dots menu, Preferences, and then find the toggle for Custom Fields and turn it on.

Showing the option to enable Custom Fields in WordPresss in the Block Editor Preferences settings. It is at the first toggle beneath the Additional settings.
The UI forces you to refresh the page when turning this feature on and off.

To add a Custom Field, type in the Key (labeled “Name”) and Value, then click Add Custom Field.

Showing a Custom Field in WordPress with a name of favorite_food and a value of burrito. There a button below the name input to add the custom field.

After it’s added, you can delete or update it from buttons below the Key/Name:

Showing a Custom Field in WordPress with a name of favorite_food and a value of burrito. There are two buttons below the name to delete or update the custom field.

After you have used Custom Fields, the keys will form into a dropdown menu for easier selection.

Showing the dropdown menu that opens when clocking on the Name field of a custom field in WordPress, allowing you to select an existing custom field.

Why use Custom Fields?

Custom Fields, along with Custom Post Types, are what make WordPress a CMS out-of-the-box rather than being limited to a simple blogging platform.

Here on CSS-Tricks, believe it or not, we use over 100 Custom Fields to do different things on this site. We tend to reach for them for relatively simple things, and it’s nice as it’s a core feature of WordPress that will continue to work forever without too much worry about compatibility or awkward technical debt.

The big idea is to open up templating possibilities. Imagine you have a page for real estate listings that has:

  • Address
  • Listing price
  • Bedrooms
  • Bathrooms
  • etc.

With custom fields, you have all that information available as discreet little chunks of data that you can echo (i.e. display) into a page template wherever you need to. That’s much more flexible than having all that data in the post content itself, even with the Block Editor.

WordPress Custom Fields use case examples

Custom Fields in WordPress can be used for so many different things! But let’s look at a five practical use cases that we have implemented here on CSS-Tricks.

1. Display additional information

Say you are publishing a video and want to have the running time of the video available to display. That’s as easy as saving the running_time as a Custom Field and displaying it wherever you’d like:

A side-by-side showing a published post on the left with the running time of a video circled in red, and the WordPress admin on the right with the running time custom field circled in the block editor showing the exact same information that is published in the post.
Note other Custom Fields in use here, like the youtube field, which we have so that we can output where the

2. Hide/Show Different Content/Features

Let’s say you want to be able to collapse the Comments area sometimes on different blog posts. You could set a custom field called should_toggle_comments and set a value of true. That’s what we do here on CSS-Tricks. In our comments.php template, we output a <ol> of all the comments, but if this custom field is there, we wrap the whole thing in a <details> element, collapsing it by default:

<?php if (get_post_meta($ post->ID, 'should_toggle_comments', true)) { ?> <details class="open-all-comments">   <summary>Toggle All Comments (there are a lot!)</summary>   <?php } ?>      <ol class="commentlist" id="commentlist">       <?php wp_list_comments('type=comment&avatar_size=512&callback=csstricks_comment'); ?>     </ol>    <?php if (get_post_meta($ post->ID, 'should_toggle_comments', true)) { ?>   </details> <?php } ?>

3. Special pull quotes

Say you have a special Category archive that displays a group of posts that contain the same category, then use a custom template for that category, like category-fancypants.php. Maybe you yank out a custom quote from each article as a custom field called main-pullquote:

<blockquote>   <?php     echo get_post_meta($ post->ID, 'main-pullquote', true);   ?> </blockquote>

That’s what we do for our annual end-of-year series:

A side by side showing the the main pull quote custom field in WordPress circled in red, and the category archive on the right with a red arrow pointing to the corresponding pull-quote that displays on the page.

4. Customize an RSS feed

We build a couple of totally custom RSS feeds here on CSS-Tricks that are different from what WordPress offers out of the box — one for videos and one for newsletters. The video feed in particular relies on some WordPress Custom Fields to output special data that is required to make the feed work as a feed for our video podcast.

Side by side showing the rss videos template in code on the left with the custom field part circled in red, and the RSS feed open in the browser on the right with an arrow pointing to where the corresponding code renders as the video enclosure.
The location of the video and the duration are both kept in custom fields

5. Hide/Show Author

Our sponsored posts here on CSS-Tricks are sometimes written to sound largely like an announcement from a company. They were written like that on purpose and likely have been written by multiple people by the time its actually published. A post like that doesn’t really need to be “by” someone. But sometimes sponsored posts are definitely authored by a specific person, even sometimes in the first person, which would be weird without showing a byline. That’s why we use a showSponsorAuthor custom field, to show that author if we need it.

<div class="sponsored-post-byline">   ❥ Sponsored   <?php if (get_post_meta($ post->ID, 'showSponsorAuthor', true)) { ?>     (Written by <?php the_author(); ?>)   <?php } ?> </div>

Above is a part of a template. We always mark a sponsored post as sponsored in the byline (example), but only optionally do we visually show the author (example), controlled by a custom field.

The APIs for displaying Custom Fields in WordPress

Most commonly, you’re looking to display the value of a single field:

<?php echo get_post_meta($ post->ID, 'mood', true); ?>

That true at the end there means “give me a single value,” meaning that even if there are multiple custom fields with the same name, you’ll only get one. To get multiple of the same name, use false, like:

<?php $ songs = get_post_meta($ post->ID, 'songs', false); ?> <h3>This post inspired by:</h3> <ul>   <?php foreach($ songs as $ song) {     echo '<li>'.$ song.'</li>';   } ?> </ul>

If you want to just dump them all out (probably mostly useful for debugging), you can do that like this:

<?php the_meta(); ?>

Although, note that this skips custom fields that start with an underscore (_), so you might consider this approach instead.

Querying for Custom Fields in WordPress

Say you wanted to query for all posts that have some particular custom field. That’s possible!

<?php $ the_query = new WP_Query(array(   'meta_key' => 'example_field_name'   'meta_value' => 'example_field_value' // as a string!  ));  if ($ the_query->have_posts()) {   while ($ the_query->have_posts()) {     $ the_query->the_post();     echo '<div>' . get_the_title() . '</div>';   } }  wp_reset_postdata();

The example above will run a query for posts that have both a custom field of example_field_name and where that field has a value of example_field_value. You could do either/or.

There is a lot more you can do here. You can use comparisons, you can get the values as numbers, and even query for multiple fields at once. We detail all that in Custom Loop/Query Based on Custom Fields.

Limiting Custom Fields in the Name dropdown

The UI dropdown for existing Custom Fields in WordPress is capped at something like 30 fields. So, if you have more than 100 different keys, the dropdown menu will look arbitrarily cut off. You can increase that number with a filter in functions.php or a plugin:

function customfield_limit_increase( $ limit ) {   $ limit = 150;   return $ limit; } add_filter( 'postmeta_form_limit', 'customfield_limit_increase' );

Any other Block Editor concerns?

The main concern is when you can’t see the custom fields UI at all. We covered how to turn it back on (because it might default to off), so always check that. The Advanced Custom Fields plugin also turns it off, so if you’re using that plugin, note there is a line below to help turn it back on (in the case you use both, as we do).

I’m not sure there is a standard way to show the value of a custom field within a block in the block editor either. If you know of a clear way, leave a comment!

Relationship to Advanced Custom Fields

The UI for native Custom Fields in WordPress is pretty… underserved. It’s not fancy, it’s got rough edges (we find that Custom Fields have a weird way of duplicating themselves on multiple post saves, for example). It doesn’t seem like Custom Fields, while native, are a particularly first-class feature of WordPress.

Advanced Custom Fields (ACF) changes that in a big way. The spirit remains the same: attach data to content. But rather than the simple string-based key-value interface that we’ve detailed, you essentially model the data with different types and it builds really nice custom UI for you to use to input that data, even integrating directly with the Block Editor.

Imagine a podcast website where each post is an individual episode. The Block Editor might be nice for written content about the episode, but probably not a good idea for all of the metadata that goes with it. The list of guests, the duration, the location of the MP3 file, the sponsor, time jump links, etc. Custom Fields are great for that, but since there are so many, you’ll be well served by Advanced Custom Fields here instead of using native Custom Fields in WordPress. Here’s a setup example of what you get as we do on the ShopTalk Show podcast:

Side by side showing the settings for custom fields in the Advanced Custom Fields plugin on the left, and those custom fields displayed on the right in the  WordPress Block Editor of a new post.

ACF, probably in an attempt to encourage using it directly and not confusing people with the native Custom Fields interface, removes the native Custom Fields interface. If you’re like us and use both types of fields, you’ll need to bring the native Custom Fields UI back to the post editor with a filter that ACF provides:

add_filter('acf/settings/remove_wp_meta_box', '__return_false');

If you use native Custom Fields in WordPress at all, you’ll want that in your functions.php file or a functionality plugin.

Note for plugin developers

Use the underscore hiding technique.

Some plugins use the Custom Fields API as a place to store post-specific data. I think that’s OK, but I’d like to implore plugin developers to always use underscore-and-plugin-prefixed custom field names when doing so.

When custom fields start with an underscore, they aren’t shown in the UI. Meaning for those of us who use the Custom Fields UI directly, it’s not cluttered with fields created by other plugins. The exception, of course, is if you intend users to be able to control what the plugin does with the Custom Field values. In that case, fine, leave those few non-underscore-prefixed fields.

_bobs_plugin_internal_value_1 // Hidden in UI _bobs_plugin_internal_value_2 // Hidden in UI bobs_plugin_config  // Shows in UI  _adrians_plugin_internal_value_1  // Hidden in UI _adrians_plugin_internal_value_2 // Hidden in UI

More examples using Custom Fields in WordPress

What do you use them for?

Do you use Custom Fields in WordPress? I’m particularly curious about native custom field usage.


How to Use Native Custom Fields in WordPress (and 5 Useful Examples) originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Why would a business push a native app over a website?

I wanted to write down what I think the reasons are here in December of 2021 so that we might revisit it from time to time in the future and see if these reasons are still relevant. I’m a web guy myself, so I’m interested in seeing how the web can evolve to mitigate these concerns.

I’m exclusively focusing on reasons a native app might either be a distinct advantage or at least feel like an advantage compared to a website. Nothing subjective here, like “it’s faster to develop on” or “native apps are more intuitive” or the like, which are too subjective to quantify. I’m also not getting into reasons where the web has the advantage. But in case you are unsure, there are many: it’s an open standardized platform, it will outlast closed systems, it strongly values backward compatibility, anybody can build for the web, it runs cross-platform, and heck, URLs alone are reason enough to go web-first. But that’s not to say native apps don’t have some extremely compelling things they offer, hence this post.

Because they get an icon on the home screen of the device.

It’s a mindshare thing. You pick up your phone, the icon is staring you in the face, begging to be opened. A widely cited report from a few years back suggests 90% of phone usage is in apps (as opposed to a mobile web browser), even though there is plenty of acknowledged gray area there. So, theoretically, you get to be part of that 90% if you make an app rather than being booted into the sad 10% zone.

If reach is the top concern, it seems like the best play is having both a web app and a native app. That way, you’re benefitting from the share-ability and search-ability that URLs give you, along with a strong presence on the device.

Looking at my own phone’s home screen, the vast majority of the apps are both native and web apps: Google Calendar, AccuWeather, Google Maps, Spotify, Notion, Front, Pocket Casts, Instagram, Discord, Twitter, GitHub, Slack, and Gmail. That’s a lot of big players doing it both ways.

Potential Solution: Both iOS and Android have “Add to Home Screen” options for websites. It’s just a fairly “buried” feature and it’s unlikely all that many people use it. Chrome on Android goes a step further, offering a Native App Install Prompt for apps for Progressive Web App (PWA) websites that meet a handful of extra criteria, like the site has been interacted with for at least 30 seconds. Native App Install Prompts are a big tool that levels this playing field, and it would be ideal to see Apple support PWAs better and offer this. There isn’t that much we can do as website authors; we wait and hope mobile operating systems make it better. There is also the whole world of software tools where what you build can be delivered both as a web app and native app, like Flutter.

Because they launch fast.

Native apps have a bunch of the resources required to run the app locally meaning they don’t need to get them from the network when opened.

Potential Solution: This is only partially true, to begin with. When you download the app, you’re downloading the resources just like the web does. The web caches resources by default, and has more advanced caching available through Service Workers. PWAs are competitive here. Native apps aren’t automatically faster.

Because it’s harder for users to block ads and easier to collect data.

There are all sorts of ad blockers for mobile.

But those work only in mobile web browsers, not native apps. If you’d like to block ads in native apps… too bad, I guess? If the point of the thing you are building is to show ads or track users, well, I guess you’ll do better with a native app, assuming you can get the same kind of traffic to it as you can a website (which is questionable).

I’m hesitant to put “because native apps are more secure” as a reason on this list because of the fact that, as a user, you have so little control over how resources load that it doesn’t feel like an increased security risk to me. But the fact that native apps typically go through an approval process before they are available in an app store does offer some degree of protection that the web doesn’t have.

Potential Solution: Allow ad/tracker blocking apps to work with native apps.

Because users stay logged in.

This is a big one! Once you’re logged in to a native app, you tend to stay logged in until you specifically log out, or a special event happens like you change your password on another device. Web apps seem to lose login status far more often than one might like, and that adds to a subconscious feeling about the app. When I open my native Twitter app, it just opens and it’s ready to use. If I thought there was a 30% chance I’d have to log in, I’m sure I’d use it far less. (And hey, that might be a good thing, but a business sure won’t think so.)

There is also a somewhat awkward thing with web apps in that, even if you’re logged in on your mobile devices primary browser, you won’t necessarily be logged into some other app’s in-app browser context — whereas, with native apps, they often intercept links and always open in the native app context.

Potential Solution: There isn’t any amazing solution here. It’s largely just trickery. For example, long cookie expiration dates (six months is about what you can get if you’re lucky, I hear). Or you can do a thing where you keep a JSON Web Token (JWT) in storage and do a rolling re-auth on it behind the scenes. There are some other solutions in this thread, many of which are a bit above my head. Making the log in experience easier is also a thing, like using oAuth or magic email links, but it’s just not the same as that “always logged in” feeling. Maybe smart browser people can think of something.

Because the apps can have that “native feel.”

Typically meaning: fast and smooth, but also that they look the way other apps on that platform look and feel. For example, Apple offers SwiftUI which is specifically for building native apps using pre-built componentry that looks Apple-y. Replicating all that on the web is going to be hard. You have to work your ass off to make it as good as what you get “for free” with something like SwiftUI.

Potential Solution: Mobile platform creators could offer UI kits that bring the design language of that mobile platform to the web. That’s largely what Google has done with Material, although the web version of it isn’t ready to use yet and is just considered a “planned” release.

Because they aren’t sharing a virtual space with competitors a tap away.

There is a sentiment that a web browser is just the wild west as you aren’t in control of where your users can run off to. If they are in your native app, good, that’s where you want them. If they are in a web browser, you’re sharing turf with untold other apps rather than keeping them on your holy ground.

Potential Solution: Get over it. Trying to hide the fact that competitors exist isn’t a good business strategy. The strength of the web is being able to hop around on a shared, standardized, open platform.

Because they get the full feature set of APIs.

All the web can hope for is the same API access as native apps have. For example, access to a camera, photos, GPS, push notifications, contacts, etc. Native apps get the APIs first, then if we’re lucky, years later, we get some kind of access on the web.

Developers might literally be forced into a native app if a crucial API is missing on the web.

One big example is push notifications. Are they generally annoying? Yes, but it’s a heavily used API and often a business requirement. And it makes plenty of sense for lots of apps (“Your turn is coming up in 500 feet,” “Your driver is here,” “Your baggage will be arriving on carousel 9,” etc.). If it’s crucial your app has good push notifications, the web isn’t an option for your app on iOS.

Potential Solution: The web should have feature parity with device APIs and new APIs should launch for both simultaneously.

Because there is an app store.

This is about discoverability. Being the one-and-only app store on a platform means you potentially get eyeballs on your app for free. If you make the best Skee-Ball game for a platform, there is a decent chance that you get good reviews and end up a top search for “Skee-Ball” in that app store, gaining you the majority share of whatever that niche market is. That’s an appealing prospect for a developer. The sea is much larger on the web, not to mention that SEO is a harder game to play and both advertising and marketing are expensive. A developer might pick a native app just because you can be a bigger fish right out of the gate than you can on the web.

And yet, if you build an app for listening to music, you’ll never beat out the major players, especially when the makers of the platform have their own apps to compete with. The web just might offer better opportunities for apps in highly competitive markets because of the wider potential audience.

Potential Solution: Allow web apps into app stores.

Because offline support is more straightforward.

The only offline capability on the web at all is via Service Workers. They are really cool, but I might argue that they aren’t particularly easy to implement, especially if the plan is using them to offer truly offline experiences for a web app that otherwise heavily relies on the network.

Native apps are less reliant on the network for everything. The core assets that make the app work are already available locally. So if a native app doesn’t need the network (say, a game), it works offline just fine. Even if it does need the network to be at its best, having your last-saved data available seems like a typical approach that many apps take.

Potential Solution: Make building offline web apps easier.


I’m a web guy and I feel like building for the web is the smart play for the vast majority of “app” situations. But I gotta admit the list of reasons for a business to go for a native app is thick enough right now that it’s no surprise that many of them do. The most successful seem to do both, despite the extreme cost of maintaining both. Like responsive design was successful in preventing us from having to build multiple versions of a website, I hope this complex situation moves in the direction of having websites be the obvious and only choice for any type of app.

CSS-Tricks

, , , , ,
[Top]

Native JavaScript Routing?

We can update the URL in JavaScript. We’ve got these APIs:

// Adds to browser history history.pushState({}, "About Page", "/about");  // Doesn't history.replaceState({}, "About Page", "/about");

JavaScript is also capable of replacing any content in the DOM.

// Hardcore document.body.innerHTML = `   <div>New body who dis.</div> `;

So with those powers combined, we can build a website where we navigate to different “pages” but the browser never refreshes. That’s literally what “Single Page App” (SPA) means.

But routing can get a bit complicated. We’re really on our own implementing it outside these somewhat low-level APIs. I’m most familiar with reaching for something like React Router, which allows the expression of routes in JSX. Something like this:

<Router>   <Switch>     <Route path="/about">       <About />     </Route>     <Route path="/users">       <Users />     </Route>     <Route path="/user/:id">       <User id={id} />     </Route>     <Route path="/">       <Home />     </Route>   </Switch> </Router>

The docs describe this bit like:

A <Switch> looks through its children <Route> and renders the first one that matches the current URL.

So it’s a little bit like a RegEx matcher with API niceties, like the ability to make a “token” with something like :id that acts as a wildcard you can pass to components to use in queries and such.

This is work! Hence the reason we have libraries to help us. But it looks like the web platform is doing what it does best and stepping in to help where it can. Over on the Google webdev blog, this is explained largely the same way:

Routing is a key piece of every web application. At its heart, routing involves taking a URL, applying some pattern matching or other app-specific logic to it, and then, usually, displaying web content based on the result. Routing might be implemented in a number of ways: it’s sometimes code running on a server that maps a path to files on disk, or logic in a single-page app that waits for changes to the current location and creates a corresponding piece of DOM to display.

While there is no one definitive standard, web developers have gravitated towards a common syntax for expressing URL routing patterns that share a lot in common with regular expressions, but with some domain-specific additions like tokens for matching path segments.

Jeff Posnick, “URLPattern brings routing to the web platform”

New tech!

const p = new URLPattern({   pathname: '/foo/:image.jpg',   baseURL: 'https://example.com', });

We can set up a pattern like that, and then run tests against it by shooting it a URL (probably the currently navigated-to one):

let result = p.test('https://example.com/foo/cat.jpg'); // true  result = p.exec('https://imagecdn1.example.com/foo/cat.jpg'); // result.hostname.groups.subdomain will be 'imagecdn1' // result.pathname.groups[0] will be 'foo', corresponding to * // result.pathname.groups.image will be 'cat'

I would think the point of all this is perhaps being able to build routing into SPAs without having to reach for libraries, making for lighter/faster websites. Or that the libraries that help us with routing can leverage it, making the libraries smaller, and ultimately websites that are lighter and faster.

This is not solid tech yet, so probably best to just read the blog post to get the gist. And use the polyfill if you want to try it out.

And speaking of the web platform showing love on SPAs lately, check out Shared Element Transitions which seems to be re-gaining momentum.


The post Native JavaScript Routing? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Native Search vs. Jetpack Instant Search in Headless WordPress With Gatsby

Have you already tried using WordPress headlessly with Gatsby? If you haven’t, you might check this article around the new Gatsby source plugin for WordPress; gatsby-source-wordpress is the official source plugin introduced in March 2021 as a part of the Gatsby 3 release. It significantly improves the integration with WordPress. Also, the WordPress plugin WPGraphQL providing the GraphQL API is now available via the official WordPress repository.

With stable and maintained tools, developing Gatsby websites powered by WordPress becomes easier and more interesting. I got myself involved in this field, I co-founded (with Alexandra Spalato), and recently launched Gatsby WP Themes — a niche marketplace for developers building WordPress-powered sites with Gatsby. In this article, I would love to share my insights and, in particular, discuss the search functionality.

Search does not come out of the box, but there are many options to consider. I will focus on two distinct possibilities — taking advantage of WordPress native search (WordPress search query) vs. using Jetpack Instant Search.

Getting started

Let’s start by setting up a WordPress-powered Gatsby website. For the sake of simplicity, I will follow the getting started instructions and install the gatsby-starter-wordpress-blog starter.

gatsby new gatsby-wordpress-w-search https://github.com/gatsbyjs/gatsby-starter-wordpress-blog 

This simple, bare-bone starter creates routes exclusively for individual posts and blog pages. But we can keep it that simple here. Let’s imagine that we don’t want to include pages within the search results.

For the moment, I will leave the WordPress source website as it is and pull the content from the starter author’s WordPress demo. If you use your own source, just remember that there are two plugins required on the WordPress end (both available via the plugin repository):

  • WPGraphQL – a plugin that runs a GraphQL server on the WordPress instance
  • WPGatsby – a plugin that modifies the WPGraphQL schema in Gatsby-specific ways (it also adds some mechanism to optimize the build process)

Setting up Apollo Client

With Gatsby, we usually either use the data from queries run on page creation (page queries) or call the useStaticQuery hook. The latter is available in components and does not allow dynamic query parameters; its role is to retrieve GraphQL data at build time. None of those two query solutions works for a user’s-initiated search. Instead, we will ask WordPress to run a search query and send us back the results. Can we send a graphQL search query? Yes! WPGraphQL provides search; you can search posts in WPGraphQL like so:

posts(where: {search: "gallery"}) {   nodes {     id     title     content   } } 

In order to communicate directly with our WPGraphQL API, we will install Apollo Client; it takes care of requesting and caching the data as well as updating our UI components.

yarn add @apollo/client cross-fetch 

To access Apollo Client anywhere in our component tree, we need to wrap our app with ApolloProvider. Gatsby does not expose the App component that wraps around the whole application. Instead, it provides the wrapRootElement API. It’s a part of the Gatsby Browser API and needs to be implemented in the gatsby-browser.js file located at the project’s root.

// gatsby-browser.js import React from "react" import fetch from "cross-fetch" import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from "@apollo/client" const cache = new InMemoryCache() const link = new HttpLink({   /* Set the endpoint for your GraphQL server, (same as in gatsby-config.js) */   uri: "https://wpgatsbydemo.wpengine.com/graphql",   /* Use fetch from cross-fetch to provide replacement for server environment */   fetch }) const client = new ApolloClient({   link,   cache, }) export const wrapRootElement = ({ element }) => (   <ApolloProvider client={client}>{element}</ApolloProvider> ) 

SearchForm component

Now that we’ve set up ApolloClient, let’s build our Search component.

touch src/components/search.js src/components/search-form.js src/components/search-results.js src/css/search.css 

The Search component wraps SearchForm and SearchResults

// src/components/search.js import React, { useState } from "react" import SearchForm from "./search-form" import SearchResults from "./search-results"  const Search = () => {   const [searchTerm, setSearchTerm] = useState("")   return (     <div className="search-container">       <SearchForm setSearchTerm={setSearchTerm} />       {searchTerm && <SearchResults searchTerm={searchTerm} />}     </div>   ) } export default Search

<SearchForm /> is a simple form with controlled input and a submit handler that sets the searchTerm state value to the user submission.

// src/components/search-form.js import React, { useState } from "react" const SearchForm = ({ searchTerm, setSearchTerm }) => {   const [value, setValue] = useState(searchTerm)   const handleSubmit = e => {     e.preventDefault()     setSearchTerm(value)   }   return (     <form role="search" onSubmit={handleSubmit}>       <label htmlFor="search">Search blog posts:</label>       <input         id="search"         type="search"         value={value}         onChange={e => setValue(e.target.value)}       />       <button type="submit">Submit</button>     </form>   ) } export default SearchForm

The SearchResults component receives the searchTerm via props, and that’s where we use Apollo Client.

For each searchTerm, we would like to display the matching posts as a list containing the post’s title, excerpt, and a link to this individual post. Our query will be like so:

const GET_RESULTS = gql`   query($  searchTerm: String) {     posts(where: { search: $  searchTerm }) {       edges {         node {           id           uri           title           excerpt         }       }     }   } `

We will use the useQuery hook from @apollo-client to run the GET_RESULTS query with a search variable.

// src/components/search-results.js import React from "react" import { Link } from "gatsby" import { useQuery, gql } from "@apollo/client" const GET_RESULTS = gql`   query($  searchTerm: String) {     posts(where: { search: $  searchTerm }) {       edges {         node {           id           uri           title           excerpt         }       }     }   } ` const SearchResults = ({ searchTerm }) => {   const { data, loading, error } = useQuery(GET_RESULTS, {     variables: { searchTerm }   })   if (loading) return <p>Searching posts for {searchTerm}...</p>   if (error) return <p>Error - {error.message}</p>   return (     <section className="search-results">       <h2>Found {data.posts.edges.length} results for {searchTerm}:</h2>       <ul>         {data.posts.edges.map(el => {           return (             <li key={el.node.id}>               <Link to={el.node.uri}>{el.node.title}</Link>             </li>           )         })}       </ul>     </section>   ) } export default SearchResults 

The useQuery hook returns an object that contains loading, error, and data properties. We can render different UI elements according to the query’s state. As long as loading is truthy, we display <p>Searching posts...</p>. If loading and error are both falsy, the query has completed and we can loop over the data.posts.edges and display the results.

if (loading) return <p>Searching posts...</p> if (error) return <p>Error - {error.message}</p> // else return ( //... )

For the moment, I am adding the <Search /> to the layout component. (I’ll move it somewhere else a little bit later.) Then, with some styling and a visible state variable, I made it feel more like a widget, opening on click and fixed-positioned in the top right corner.

Paginated queries

Without the number of entries specified, the WPGraphQL posts query returns ten first posts; we need to take care of the pagination. WPGraphQL implements the pagination following the Relay Specification for GraphQL Schema Design. I will not go into the details; let’s just note that it is a standardized pattern. Within the Relay specification, in addition to posts.edges (which is a list of { cursor, node } objects), we have access to the posts.pageInfo object that provides:

  • endCursor – cursor of the last item in posts.edges,
  • startCursor – cursor of the first item in posts.edges,
  • hasPreviousPage – boolean for “are there more results available (backward),” and
  • hasNextPage – boolean for “are there more results available (forward).”

We can modify the slice of the data we want to access with the additional query variables:

  • first – the number of returned entries
  • after – the cursor we should start after

How do we deal with pagination queries with Apollo Client? The recommended approach is to use the fetchMore function, that is (together with loading, error and data) a part of the object returned by the useQuery hook.

// src/components/search-results.js import React from "react" import { Link } from "gatsby" import { useQuery, gql } from "@apollo/client" const GET_RESULTS = gql`   query($  searchTerm: String, $  after: String) {     posts(first: 10, after: $  after, where: { search: $  searchTerm }) {       edges {         node {           id           uri           title         }       }       pageInfo {         hasNextPage         endCursor       }     }   } ` const SearchResults = ({ searchTerm }) => {   const { data, loading, error, fetchMore } = useQuery(GET_RESULTS, {     variables: { searchTerm, after: "" },   })   if (loading && !data) return <p>Searching posts for {searchTerm}...</p>   if (error) return <p>Error - {error.message}</p>   const loadMore = () => {     fetchMore({       variables: {         after: data.posts.pageInfo.endCursor,       },       // with notifyOnNetworkStatusChange our component re-renders while a refetch is in flight so that we can mark loading state when waiting for more results (see lines 42, 43)       notifyOnNetworkStatusChange: true,     })   }    return (     <section className="search-results">       {/* as before */}       {data.posts.pageInfo.hasNextPage && (         <button type="button" onClick={loadMore} disabled={loading}>           {loading ? "Loading..." : "More results"}         </button>       )}     </section>   ) } export default SearchResults 

The first argument has its default value but is necessary here to indicate that we are sending a paginated request. Without first, pageInfo.hasNextPage will always be false, no matter the search keyword.

Calling fetchMore fetches the next slice of results but we still need to tell Apollo how it should merge the “fetch more” results with the existing cached data. We specify all the pagination logic in a central location as an option passed to the InMemoryCache constructor (in the gatsby-browser.js file). And guess what? With the Relay specification, we’ve got it covered — Apollo Client provides the relayStylePagination function that does all the magic for us.

// gatsby-browser.js import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from "@apollo/client" import { relayStylePagination } from "@apollo/client/utilities" const cache = new InMemoryCache({   typePolicies: {     Query: {       fields: {         posts: relayStylePagination(["where"]),       },     },   }, }) /* as before */ 

Just one important detail: we don’t paginate all posts, but instead the posts that correspond to a specific where condition. Adding ["where"] as an argument to relayStylePagination creates a distinct storage key for different search terms.

Making search persistent

Right now my Search component lives in the Layout component. It’s displayed on every page but gets unmounted every time the route changes. What if we could keep the search results while navigating? We can take advantage of the Gatsby wrapPageElement browser API to set persistent UI elements around pages.

Let’s move <Search /> from the layout component to the wrapPageElement:

// gatsby-browser.js import Search from "./src/components/search" /* as before */ export const wrapPageElement = ({ element }) => {   return <><Search />{element}</> } 

The APIs wrapPageElement and wrapRootElement exist in both the browser and Server-Side Rendering (SSR) APIs. Gatsby recommends that we implement wrapPageElement and wrapRootElement in both gatsby-browser.js and gatsby-ssr.js. Let’s create the gatsby-ssr.js (in the root of the project) and re-export our elements:

// gatsby-ssr.js export { wrapRootElement, wrapPageElement } from "./gatsby-browser" 

I deployed a demo where you can see it in action. You can also find the code in this repo.

The wrapPageElement approach may not be ideal in all cases. Our search widget is “detached” from the layout component. It works well with the position “fixed” like in our working example or within an off-canvas sidebar like in this Gatsby WordPress theme.

But what if you want to have “persistent” search results displayed within a “classic” sidebar? In that case, you could move the searchTerm state from the Search component to a search context provider placed within the wrapRootElement:

// gatsby-browser.js import SearchContextProvider from "./src/search-context" /* as before */ export const wrapRootElement = ({ element }) => (   <ApolloProvider client={client}>     <SearchContextProvider>       {element}     </SearchContextProvider>   </ApolloProvider> ) 

…with the SearchContextProvider defined as below:

// src/search-context.js import React, {createContext, useState} from "react" export const SearchContext = createContext() export const SearchContextProvider = ({ children }) => {   const [searchTerm, setSearchTerm] = useState("")   return (     <SearchContext.Provider value={{ searchTerm, setSearchTerm }}>       {children}     </SearchContext.Provider>   ) }

You can see it in action in another Gatsby WordPress theme:

Note how, since Apollo Client caches the search results, we immediately get them on the route change.

Results from posts and pages

If you checked the theme examples above, you might have noticed how I deal with querying more than just posts. My approach is to replicate the same logic for pages and display results for each post type separately.

Alternatively, you could use the Content Node interface to query nodes of different post types in a single connection:

const GET_RESULTS = gql`   query($  searchTerm: String, $  after: String) {     contentNodes(first: 10, after: $  after, where: { search: $  searchTerm }) {       edges {         node {           id           uri           ... on Page {             title           }           ... on Post {             title             excerpt           }         }       }       pageInfo {         hasNextPage         endCursor       }     }   } `

Our solution seems to work but let’s remember that the underlying mechanism that actually does the search for us is the native WordPress search query. And the WordPress default search function isn’t great. Its problems are limited search fields (in particular, taxonomies are not taken into account), no fuzzy matching, no control over the order of results. Big websites can also suffer from performance issues — there is no prebuilt search index, and the search query is performed directly on the website SQL database.

There are a few WordPress plugins that enhance the default search. Plugins like WP Extended Search add the ability to include selected meta keys and taxonomies in search queries.

The Relevanssi plugin replaces the standard WordPress search with its search engine using the full-text indexing capabilities of the database. Relevanssi deactivates the default search query which breaks the WPGraphQL where: {search : …}. There is some work already done on enabling Relevanssi search through WPGraphQL; the code might not be compatible with the latest WPGraphQL version, but it seems to be a good start for those who opt for Relevanssi search.

In the second part of this article, we’ll take one more possible path and have a closer look at the premium service from Jetpack — an advanced search powered by Elasticsearch. By the way, Jetpack Instant search is the solution adopted by CSS-Tricks.

Using Jetpack Instant Search with Gatsby

Jetpack Search is a per-site premium solution by Jetpack. Once installed and activated, it will take care of building an Elasticsearch index. The search queries no longer hit the SQL database. Instead, the search query requests are sent to the cloud Elasticsearch server, more precisely to:

https://public-api.wordpress.com/rest/v1.3/sites/{your-blog-id}/search 

There are a lot of search parameters to specify within the URL above. In our case, we will add the following:

  • filter[bool][must][0][term][post_type]=post: We only need results that are posts here, simply because our Gatsby website is limited to post. In real-life use, you might need spend some time configuring the boolean queries.
  • size=10 sets the number of returned results (maximum 20).
  • with highlight_fields[0]=title, we get the title string (or a part of it) with the searchTerm within the <mark> tags.
  • highlight_fields[0]=content is the same as below but for the post’s content.

There are three more search parameters depending on the user’s action:

  • query: The search term from the search input, e.g. gallery
  • sort: how the results should be orderer, the default is by score "score_default" (relevance) but there is also "date_asc" (newest) and "date_desc" (oldest)
  • page_handle: something like the “after” cursor for paginated results. We only request 10 results at once, and we will have a “load more” button.

Now, let’s see how a successful response is structured:

{   total: 9,   corrected_query: false,   page_handle: false, // or a string it the total value > 10   results: [     {       _score: 196.51814,       fields: {         date: '2018-11-03 03:55:09',         'title.default': 'Block: Gallery',         'excerpt.default': '',         post_id: 1918,         // we can configure what fields we want to add here with the query search parameters       },       result_type: 'post',       railcar: {/* we will not use this data */},       highlight: {         title: ['Block: <mark>Gallery</mark>'],         content: [           'automatically stretch to the width of your <mark>gallery</mark>. ... A four column <mark>gallery</mark> with a wide width:',           '<mark>Gallery</mark> blocks have two settings: the number of columns, and whether or not images should be cropped',         ],       },     },     /* more results */   ],   suggestions: [], // we will not use suggestions here   aggregations: [], // nor the aggregations }

The results field provides an array containing the database post IDs. To display the search results within a Gatsby site, we need to extract the corresponding post nodes (in particular their uri ) from the Gatsby data layer. My approach is to implement an instant search with asynchronous calls to the rest API and intersect the results with those of the static GraphQL query that returns all post nodes.

Let’s start by building an instant search widget that communicates with the search API. Since this is not specific to Gatsby, let’s see it in action in this Pen:

Here, useDebouncedInstantSearch is a custom hook responsible for fetching the results from the Jetpack Search API. My solution uses the awesome-debounce-promise library that allows us to take some extra care of the fetching mechanism. An instant search responds to the input directly without waiting for an explicit “Go!” from the user. If I’m typing fast, the request may change several times before even the first response arrives. Thus, there might be some unnecessary network bandwidth waste. The awesome-debounce-promise waits a given time interval (say 300ms) before making a call to an API; if there is a new call within this interval, the previous one will never be executed. It also resolves only the last promise returned from the call — this prevents the concurrency issues.

Now, with the search results available, let’s move back to Gatsby and build another custom hook:

import {useStaticQuery, graphql} from "gatsby"  export const useJetpackSearch = (params) => {   const {     allWpPost: { nodes },   } = useStaticQuery(graphql`     query AllPostsQuery {       allWpPost {         nodes {           id           databaseId           uri           title           excerpt         }       }     }   `)   const { error, loading, data } = useDebouncedInstantSearch(params)   return {     error,     loading,     data: {       ...data,       // map the results       results: data.results.map(el => {         // for each result find a node that has the same databaseId as the result field post_id         const node = nodes.find(item => item.databaseId === el.fields.post_id)         return {           // spread the node           ...node,           // keep the highlight info           highlight: el.highlight         }       }),     }   } }

I will call the useJetpackSearch within <SearchResults />. The Gatsby-version of <SearchResults /> is almost identical as that in the Pen above. The differences are highlighted in the code block below. The hook useDebouncedInstantSearch is replaced by useJetpackSearch (that calls the former internally). There is a Gatsby Link that replaces h2 as well as el.fields["title.default"] and el.fields["excerpt.default"] are replaced by el.title and el.excerpt.

const SearchResults = ({ params, setParams }) => {   const { loading, error, data } = useJetpackSearch(params)   const { searchTerm } = params   if (error) {     return <p>Error - {error}</p>   }   return (     <section className="search-results">       {loading ? (         <p className="info">Searching posts .....</p>       ) : (         <>           {data.total !== undefined && (             <p>               Found {data.total} results for{" "}               {data.corrected_query ? (                 <>                   <del>{searchTerm}</del> <span>{data.corrected_query}</span>                 </>               ) : (                 <span>{searchTerm}</span>               )}             </p>           )}         </>       )}       {data.results?.length > 0 && (         <ul>           {data.results.map((el) => {             return (               <li key={el.id}>                 <Link to={el.uri}>                   {el.highlight.title[0]                     ? el.highlight.title.map((item, index) => (                         <React.Fragment key={index}>                           {parse(item)}                         </React.Fragment>                       ))                     : parse(el.title)}                 </Link>                 <div className="post-excerpt">                   {el.highlight.content[0]                     ? el.highlight.content.map((item, index) => (                         <div key={index}>{parse(item)}</div>                       ))                     : parse(el.excerpt)}                 </div>               </li>             );           })}         </ul>       )}       {data.page_handle && (         <button           type="button"           disabled={loading}           onClick={() => setParams({ pageHandle: data.page_handle })}         >           {loading ? "loading..." : "load more"}         </button>       )}     </section>   ) } 

You can find the complete code in this repo and see it in action in this demo. Note that I no longer source WordPress data from the generic WordPress demo used by Gatsby starter. I need to have a website with Jetpack Search activated.

Wrapping up

We’ve just seen two ways of dealing with search in headless WordPress. Besides a few Gatsby-specific technical details (like using Gatsby Browser API), you can implement both discussed approaches within other frameworks. We’ve seen how to make use of the native WordPress search. I guess that it is an acceptable solution in many cases.

But if you need something better, there are better options available. One of them is Jetpack Search. Jetpack Instant Search does a great job on CSS-Tricks and, as we’ve just seen, can work with headless WordPress as well. There are probably other ways of implementing it. You can also go further with the query configuration, the filter functionalities, and how you display the results.


The post Native Search vs. Jetpack Instant Search in Headless WordPress With Gatsby appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

CSS Modules (The Native Ones)

They are actually called “CSS Module Scripts” and are a native browser feature, as opposed to the popular open-source project that essentially does scoped styles by creating unique class name identifiers in both HTML and CSS.

Native CSS Modules are a part of ES Modules (a lot like JSON modules we recently covered):

// Regular ES Modules import React from "https://cdn.skypack.dev/react@17.0.1";  // Newfangled JSON Modules import configData from './config-data.json' assert {type: 'json'};  // Newfangled CSS Modules import styleSheet from "./styles.css" assert { type: "css" };

I first saw this from Justin’s tweet:

This is a Chrome-thing for now. Relevant links:

As I write, it only works in Chrome Canary with the Experimental Web Platform Features on. If your question is, When can I use this on production projects with a wide variety of users using whatever browser they want? I’d say: I have no idea. Probably years away. Maybe never. But it’s still interesting to check out. Maybe support will move fast. Maybe you’ll work on an Electron project or something where you can count on specific browser features.

This looks like an extension of Constructable Stylesheets, which are also Chrome-only, so browsers that are “behind” on this would have to start there.

I gave Justin’s idea a spin here:

If I log what I get back from the CSS Modules import, it’s a CSSStyleSheet:

Showing a styles tree with nodes for CSS rules, media, rules, and prototype.

If you’re going to actually use the styles… that’s on you. Justin’s idea basically applies the style as a one-liner because it just so happens that lit-html supports CSSStyleSheet (those docs don’t make that clear, but I imagine they will at some point). For native web components, it’s not much different: you import it, get the CSSStyleSheet, then apply it to the web component like:

this.shadowRoot.adoptedStyleSheets = [myCSSStyleSheet];

I would think the point of all this is:

  • we needed a way to import a stylesheet in JavaScript and this is basically the first really clear crack at it that I’m aware of,
  • now we have programatic access to manipulate the CSS before using it, if we wanted, and
  • it looks particularly nice for Web Components usage, but it’s generic. Do whatever you want with the stylesheet once you have it.

The post CSS Modules (The Native Ones) appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Meet `:has`, A Native CSS Parent Selector

The reasons that are often cited that make container queries difficult or impossible is things like infinite loops—e.g. changing the width of an element, invalidating a container query, which changes the width again, which makes the container query take effect, etc. But that was solved with containment. A “parent selector”, or :has as it is now been officially dubbed (I like it, that’s how jQuery rolled, although Adrian pointed out a tweet noting that it’s more versatile), has traditionally had similar problems. Things like requiring “multiple pass” rendering which is too slow to be acceptable.

Brian Kardell says:

Primarily, even without :has() it’s pretty hard to live up to performance guarantees of CSS, where everything continue to evaluate and render “live” at 60fps. If you think, mathematically, about just how much work is conceptually involved in applying hundreds or thousands of rules as the DOM changes (including as it is parsing), it’s quite a feat as is.

Engines have figured out how to optimize this based on clever patterns and observations that avoid the work that is conceptually necessary – and a lot of that is sort of based on these subject invariants that has() would appear to throw to the wind.

The fact that there is a spec now is super encouraging, and that it has Igalia’s eye on it. Apparently, some of the performance problems have either been surmounted or, through testing, determined to be negligible enough to remain a shippable feature.

Adrian Bece digs into it all!

The team at Igalia has worked on some notable web engine features like CSS grid and container queries, so there is a chance for :has selector to see the light of day, but there is still a long way to go.

What makes relational selector one of the most requested features in the past few years and how are the developers working around the missing selector? In this article, we’re going to answer those questions and check out the early spec of :has selector and see how it should improve the styling workflow once it’s released.

Let’s cross our fingers. I’ve been watching this for 10 years and trying to document use cases.

Direct Link to ArticlePermalink


The post Meet `:has`, A Native CSS Parent Selector appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Still Hoping for Better Native Page Transitions

It sure would be nice to be able to animate the transition between pages if we want to on the web, at least without resorting to hacks or full-blown architecture choices just to achieve it. Some kind of API that would run stuff (it could integrate with WAAPI?) before the page is unloaded, and then some buddy API that would do the same on the way in.

We do have an onbeforeunload API, but I’m not sure what kind of baggage that has. That, or otherwise, is all possible now, but what I want are purpose-built APIs that help us do it cleanly (understandable functions) and with both performance (working as quickly as clicking links normally does) and accessibility (like focus handling) in mind.

If you’re building a single-page app anyway, you get the freedom to animate between views because the page never reloads. The danger here is that you might pick a single-page app just for this ability, which is what I mean by having to buy into a site architecture just to achieve this. That feels like an unfortunate trade-off, as single-page apps bring a ton of overhead, like tooling and accessibility concerns, that you wouldn’t have otherwise needed.

Without a single page app, you could use something like Turbo and animate.css like this. Or, Adam’s new transition.style, a clip-path() based homage to Daniel Edan’s masterpiece. Maybe if that approach was paired with instant.page it would be as fast as any other internal link click.

There are other players trying to figure this out, like smoothState.js and Swup. The trick being: intercept the action to move to the next page, run the animation first, then load the next page, and animate the new page in. Technically, it slows things down a bit, but you can do it pretty efficiently and the movement adds enough distraction that the perceived performance might even be better.

Ideally, we wouldn’t have to animate the entire page but we could have total control to make more interesting transitions. Heck, I was doing that a decade ago with a page for a musician where clicking around the site just moved things around so that the audio would keep playing (and it was fun).

This would be a great place for the web platform to step in. I remember Jake pushed for this years ago, but I’m not sure if that went anywhere. Then we got portals which are… ok? Those are like if you load an iframe on the page and then animate it to take over the whole page (and update the URL). Not much animation nuance possible there, but you could certainly swipe some pages around or fade them in and out (hey here’s another one: Highway), like jQuery Mobile did back in ancient times.


The post Still Hoping for Better Native Page Transitions appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , ,
[Top]

Imagining native skip links

I love it when standards evolve from something that a bunch of developers are already doing, and making it easier and foolproof. Kitty Giraudel is onto that here with skip links, something that every website should probably have, and that has a whole checklist of things that we can and do screw up:

  • It should be the first thing to tab into.
  • It should be hidden carefully so it remains focusable.
  • When focused, it should become visible.
  • Its content should start with “Skip” to be easily recognisable.
  • It should lead to the main content of the page.

Doing this natively could solve all those problems and more (like displaying in the correct language for that user). Nice little project for someone to mock up as a browser extension, I’d say.

Reminds me of the idea of extending the Web Share API into native HTML. It’s just a good idea.

Direct Link to ArticlePermalink


The post Imagining native skip links appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

Let’s Create a Lightweight Native Event Bus in JavaScript

An event bus is a design pattern (and while we’ll be talking about JavaScript here, it’s a design pattern in any language) that can be used to simplify communications between different components. It can also be thought of as publish/subscribe or pubsub.

The idea is that components can listen to the event bus to know when to do the things they do. For example, a “tab panel” component might listen for events telling it to change the active tab. Sure, that might happen from a click on one of the tabs, and thus handled entirely within that component. But with an event bus, some other elements could tell the tab to change. Imagine a form submission which causes an error that the user needs to be alerted to within a specific tab, so the form sends a message to the event bus telling the tabs component to change the active tab to the one with the error. That’s what it looks like aboard an event bus.

Pseudo-code for that situation would be like…

// Tab Component Tabs.changeTab = id =&gt; {   // DOM work to change the active tab. } MyEventBus.subscribe("change-tab", Tabs.changeTab(id));  // Some other component... // something happens, then: MyEventBus.publish(&quot;change-tab&quot;, 2);  

Do you need a JavaScript library to this? (Trick question: you never need a JavaScript library). Well, there are lots of options out there:

Also, check out Mitt which is a library that’s only 200 bytes gzipped. There is something about this simple pattern that inspires people to tackle it themselves in the most succincet way possible.

Let’s do that ourselves! We’ll use no third-party library at all and leverage an event listening system that is already built into JavaScript with the addEventListener we all know and love.

First, a little context

The addEventListener API in JavaScript is a member function of the EventTarget class. The reason we can bind a click event to a button is because the prototype interface of <button> (HTMLButtonElement) inherits from EventTarget indirectly.

Source: MDN Web Docs

Different from most other DOM interfaces, EventTarget can be created directly using the new keyword. It is supported in all modern browsers, but only fairly recently. As we can see in the screenshot above, Node inherits EventTarget, thus all DOM nodes have method addEventListener.

Here’s the trick

I’m suggesting an extremely lightweight Node type to act as our event-listening bus: an HTML comment (<!-- comment -->).

To a browser rendering engine, HTML comments are just notes in the code that have no functionality other than descriptive text for developers. But since comments are still written in HTML, they end up in the DOM as real nodes and have their own prototype interface—Comment—which inherits Node.

The Comment class can be created from new directly like EventTarget can:

const myEventBus = new Comment('my-event-bus');

We could also use the ancient, but widely-supported document.createComment API. It requires a data parameter, which is the content of the comment. It can even be an empty string:

const myEventBus = document.createComment('my-event-bus');

Now we can emit events using dispatchEvent, which accepts an Event Object. To pass user-defined event data, use CustomEvent, where the detail field can be used to contain any data.

myEventBus.dispatchEvent(   new CustomEvent('event-name', {      detail: 'event-data'   }) );

Internet Explorer 9-11 supports CustomEvent, but none of the versions support new CustomEvent. It’s complex to simulate it using document.createEvent, so if IE support is important to you, there’s a way to polyfill it.

Now we can bind event listeners:

myEventBus.addEventListener('event-name', ({ detail }) => {   console.log(detail); // => event-data });

If an event intends to be triggered only once, we may use { once: true } for one-time binding. Other options won’t fit here. To remove event listeners, we can use the native removeEventListener.

Debugging

The number of events bound to single event bus can be huge. There also can be memory leaks if you forget to remove them. What if we want to know how many events are bound to myEventBus?

myEventBus is a DOM node, so it can be inspected by DevTools in the browser. From there, we can find the events in the Elements → Event Listeners tab. Be sure to uncheck “Ancestors” to hide events bound on document and window.

An example

One drawback is that the syntax of EventTarget is slightly verbose. We can write a simple wrapper for it. Here is a demo in TypeScript below:

class EventBus<DetailType = any> {   private eventTarget: EventTarget;   constructor(description = '') { this.eventTarget = document.appendChild(document.createComment(description)); }   on(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.addEventListener(type, listener); }   once(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.addEventListener(type, listener, { once: true }); }   off(type: string, listener: (event: CustomEvent<DetailType>) => void) { this.eventTarget.removeEventListener(type, listener); }   emit(type: string, detail?: DetailType) { return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail })); } }      // Usage const myEventBus = new EventBus<string>('my-event-bus'); myEventBus.on('event-name', ({ detail }) => {   console.log(detail); });  myEventBus.once('event-name', ({ detail }) => {   console.log(detail); });  myEventBus.emit('event-name', 'Hello'); // => HellonHello myEventBus.emit('event-name', 'World'); // => World

The following demo provides the compiled JavaScript.


And there we have it! We just created a dependency-free event-listening bus where one component can inform another component of changes to trigger an action. It doesn’t take a full library to do this sort of stuff, and the possibilities it opens up are pretty endless.


The post Let’s Create a Lightweight Native Event Bus in JavaScript appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , ,
[Top]