Tuesday, January 24, 2023

Tailwind

 With great UX comes great CSS responsibility. At Yoast, like most other companies, we witnessed first-hand that writing a scalable and maintainable CSS codebase is complicated. We’ve tried everything from plain CSS, preprocessors and CSS-in-JS options to all kinds of frameworks. But we never got a firm grasp on dealing with consistency, exceptions and learning curves. Finally, with Tailwind, we feel we’ve found a durable solution: stop writing CSS altogether.

Tailwind is a utility-first CSS framework and the concept is simple: combine single-purpose CSS classes on your elements to get the styling you need. Instead of combining CSS properties in your stylesheets to get there. Can you see how this immediately solves the scalability issue? Writing CSS becomes a matter of finding the right combinations of utilities instead of, well, writing actual CSS. Of course, Tailwind provides a solution for combining utilities into new class names for easy application, but you’re still not really writing CSS. Tailwind comes with great documentation including many examples, is easy to configure and provides great performance by purging unused styles. I recommend it to everyone.

// Combine multiple Tailwind utility classes into a custom
// .button class using the @apply directive.

.yst-section {
  @apply yst-max-w-5xl lg:yst-grid lg:yst-grid-cols-3 lg:yst-gap-8 yst-mx-8 yst-mb-8 yst-border-b yst-border-gray-200 yst-pb-8;
}

Platform agnosticism

Now let’s get back to that thing I said about this front-end not being built for Shopify at all. With this project, we set out to create a universal Yoast interface, not specifically tailored to one platform. So instead of building a user interface, we will actually be building an interface for building user interfaces. A higher-order user interface, if you will. This kind of agnostic approach offers challenges of its own, mainly those considering flexibility.

Configurability

Not all platforms offer the same editable content types. And some platforms might not even benefit or support certain SEO features at all. In other words, the front-end has to be highly configurable to fit the needs and capabilities of many platforms. Almost like an SEO settings form builder.

We solved this technically by exposing an app initializer function that accepts the configuration for the specific platform at hand. This object mainly consists of a list of content types and their supported SEO features and a list of generally supported SEO features. Based on this configuration, the initializing function creates a Redux store and other contexts, sets up routes and form components and registers callbacks for handling retrieving and saving data. It then returns an app object with a render method. Which is basically just a ReactDOM.render call with its first argument, the component, already supplied. Now the implementor can render the app in a DOM element of choosing while remaining unaware of the technology responsible for that rendering.

This approach has proven to offer the flexibility and decoupling we were looking for. Everything the user interface needs to know from the implementor is shared through a single configuration object. In theory, we could now switch out Redux for React’s useReducer. Or replace React with Vue entirely without having to touch the implementor whatsoever. Magic…

// A basic example of how to initialize
// and render the new Yoast user interface.
import initializeSettings from "@yoast/admin-ui/settings";

const { render } = initializeSettings( {
  isSetting1Enabled: true,
  isSettings2Enabled: false,
  contentTypes: [
    {
      name: "post",
      isContentTypeSetting1Enabled: false,
      ...etc,
    },
  ],
  handleSave: async ( data ) => {
    const response = await saveData( data );
    return response.status;
  },
  ...etc,
} );

// Start rendering React in the #root.
render( document.getElementById( "root" ) );

No comments:

Post a Comment