Skip to the content.

WordPress Interfaces

We have some general standards and expectations around building custom interfaces in WordPress.

Our WordPress products rarely exist in environments we fully control, so like all WordPress code we’re careful with scoping assets to only load where necessary, CSS selector specificity to avoid style collisions and runtime globals other codebases can use and manipulate.

We also need to be considerate of how and where we take up interface real estate in a WordPress install.

This document isn’t exhaustive, please feel free to add to it and go beyond it!

Generally lean on @wordpress packages when building and extending in WordPress interfaces, especially before relying on third-party vendor dependencies

At writing there are nearly 100 packages built and maintained by the WordPress contributor community.

WordPress also ships with external libraries including:

Encouraged

Discouraged:

Admin and public body classes or HTML classes

The WordPress Admin and WordPress frontend both use CSS classnames on the <body> and <html> element to surface conditions and data about the state of the application.

These are a great way to scope CSS styles, use for feature or conditional checks in JavaScript and can be extended in PHP or JavaScript.

Examples of use:

PHP Filters:

Don’t forget WordPress’ Admin Color Scheme classes

Due to WordPress’ Admin Color Schemes, it’s often best to use Core’s helper classes when not building branded interfaces (at minimum, check a what a branded UI looks like in each of WordPress’ included color schemes).

Background Classes

Text Classes

NOTE: Be wary of .wp-ui-text-primary and always check usage with the Light admin color scheme – you might need to override this class within your interface if you use it!

Use stable @wordpress/components and UI patterns

The @wordpress/components system is preferred over custom styles or third-party component systems for a multitude of reasons including interface and experience consistiency, accesssibility testing, smaller builds and the performance benefits of cached JavaScript and CSS in CDNs and browsers from requests in other WordPress interfaces.

In short, most third-party component systems don’t inject well into 18 years worth of CSS specificity and stylesheets that are ever-evolving each WordPress release. They also normally duplicate interfaces users are accustomed to in WordPress, which creates context switching and additional assets to transmit and cache in users’ browsers.

At time of writing, there is not yet an official CSS Variable system for the entire WordPress Admin, but the @wordpress/components accept a number of color and miscellaneous variables for customization at runtime.

/* In this example, the WordPress Admin blue hues are swapped for Newfold's Tangerine Orange hues */
:root {
    /* Primary color used for Buttons, ToggleControl bg's and other accents. */
    --wp-admin-theme-color: #ef7832;
    --wp-admin-theme-color--rgb: 239, 120, 50; /* note no rgb()! */
    /* Used for 10% darker states in components -- i.e. hover, focus. */
    --wp-admin-theme-color-darker-10: #dd5228;
    --wp-admin-theme-color-darker-10--rgb: 221, 82, 40; /* note no rgb()! */
    /* User for 20% darker states in components -- i.e. active, active-focus */
    --wp-admin-theme-color-darker-20: #c13311;
    --wp-admin-theme-color-darker-20--rgb: 193, 51, 17; /* note no rgb()! */
    /* Width of focus rings on certain elements like Buttons -- 0px to remove */
    --wp-admin-border-width-focus: 2px;
}

When necessary, light CSS class-based extension, component reuse within abstracted components and then fully-custom components are preferred in that order.

Use @wordpress/base-styles

The @wordpress/base-styles package contains reliable references for Admin breakpoints, colors, z-indexes and animations, as well as Sass mixins for CSS reset, element styles and many other necessary aspects of designing within WordPress.

Use of the package directly in an extending Sass file is preferred over reference and duplication to inherit updates as they make it into the Gutenberg project and WordPress Core.

These styles are tested cross-browser, are leveraged by WordPress itself, are considerate of many nuances like whether the Admin Menu is expanded or collapsed and the variance in height of the Admin Bar at mobile and desktop breakpoints. In short, without using these styles you’re much more likely to miss something important.

Use Flexbox and Grid for responsive layouts

Legacy box-model layouts that rely on widths, margins and padding or tables and percents are discouraged, as they require more code to express and are brittle to maintain.

Grid often allows for the smallest file sizes and easiest maintance, so we generally prefer it. However, Flexbox does lend itself to some interfaces so Grid, Grid with a sprinkle of Flexbox and Flexbox are all acceptable in that preferred order.

WordPress allows the filtering of available action links in the Plugin List Table. Providing a link into the Settings or features of a Plugin is a practice we generally like to follow. WordPress dynamically creates a filter called plugin_action_links_{DIR}/{MAIN_PLUGIN_FILE.php} (ex. plugin_action_links_wp-plugin-web/wp-plugin-web.php).

Filtering the array of available links you can pass in additional links such as a link to the settings, incomplete Setup flow, etc.

Example

add_filter( 'plugin_action_links_wp-plugin-newfold/wp-plugin-newfold.php', 'nfd_callable_plugin_action_links' );
function nfd_callable_plugin_action_links( $links ) {
    $settings_url = add_query_arg( 'page', 'newfold-settings', get_admin_url() . 'admin.php' );
    $new_links = array(
        'settings' => '<a href="' . esc_url( $settings_url ) . '">' . __( 'Settings', 'wp-plugin-newfold' ) . '</a>';
    );
    if ( false === get_option( 'nfd_wp_plugin_newfold_setup_complete' ) ) {
        $setup_url = add_query_arg( 'page', 'newfold-setup', get_admin_url() . 'admin.php' );
        $new_links['setup'] = '<a href="' . esc_url( $setup_url ) . '"><strong>' . __( 'Continue Setup', 'wp-plugin-newfold' ) . '</strong></a>';
    }

    return array_merge( $new_links, $links );
}

Sparingly Register Top-Level Admin Menu Items

With few exceptions, WordPress Admin Menu items should always be registered under an existing top-level menu item or the Newfold brand’s top-level menu item. There are other more appropriate avenues to address discovery.

Sparingly Register Admin Bar items

With few exceptions, notices and menu items should be added to the WordPress Admin Bar when contextually necessary and only when they are a top priority for users.

NOTE: The Admin Bar is unique in that it is loaded in both the WordPress Backend and most frontends for authenticated users. For consistiency, many Admin Bar extension should take place in both contexts and therefore is_admin(), admin_enqueue_scripts and Admin-only hooks are not good places for Admin Bar extension, as they will leave behind the frontend. There are some circumstances where front-end only or admin-only extension is desirable and intended.

In your codebase, Admin Bar code shouldn’t be colocated with Admin-only code despite partially sharing a name.

Sparingly Register Admin Notifications & Banners

With few exceptions, all banners and notifications should be scoped to specific screens and allow user dismissal (that is retained between page loads and sessions).

In general, we avoid disrupting users and contributing to the UX problem of over-notification in the WordPress ecosystem.

Never register non-emergency modals that block plugin deactivation

We consider it a dark pattern to popup a survey asking users why they’re deactivating a plugin. While it’s understandable to want to understand why a user is deactivating a plugin, there are more respectful, non-blocking experiences like using JavaScript to insert messaging into the plugin row or page header. Blocking deactivation can create an annoying, frustrating experience for users and can have undesirable secondary effects.

If there is an emergency-level reason to justify loading a confirmation dialog or informative popup, this friction is in the users’ best interest but if it’s just about soliciting feedback or “hate to see you go” please refrain.

If you have an onboarding or guidance flow, make it something that can be relaunched

If you build a flow for your product that guides a user through configuration or teaches a workflow, make it’s an expereince users can return to. Many guidance flows are presented on first-run or configuration flows on initial activation with no resource to return.

Even if all the options and tools are available elsewhere, flows help teach best practices and build mental models for users. Having a way to launch into these flows from help and options pages (with the ability to reset existing configuration) is an invaluable, interactive avenue for self-help for users.

Consider placing SlotFill’s in your JavaScript interfaces

WordPress uses a custom approach to portal rendering called SlotFill’s where named slots act as insertion or fill points for additional components to be added to the React tree. Interfaces like the Block Editor have multiple slots for third-party plugins to extend. SlotFills offer great opportunities make interfaces extendable by third-party codebases.

Register references in Tools and Settings

While it’s often preferred to keep Tools and Settings contained within an application’s primary interface, registering them at the bottom of the Tools and Settings pages default to WordPress makes discovery easier, particularly for users with an abundance of Plugins.

Which leads into the next item…

It isn’t enough to simply make a Single-Page Application with active tabs or portals. The ability to deep-link into locations within the app is invaluable for hosting products, marketing campaigns, support situations and guidance flows.

In complex, multi-page applications, we prefer a HashRouter from react-router-dom.

In simple situations where a client-side router is excessive, PHP or JavaScript should be used to handle the triggering of functionality.

In general, <iframe>’s are a last resort

Frames can rapidly unlock integration opportunities and empower users, but they present a multitude of limitations, performance headaches and security considertations.

WordPress also takes steps to prevent cross-origin frames that we can attempt to work-around, but aren’t exclusively within our control. Due to performance, security and these challenges, we strongly prefer a native WordPress experience in lieu of <iframe>’s.

When we do frame-in an experience, we expect that experience to be as seamless as possible, without any horizontal scrolling and maximizing the use of available real estate to present the frame. To this end, a library such as pym.js to postMessage() viewport sizing between parent and child frames might be necessary.

To get sign-off on frame-based solutions:

As a general philosophy, remember your interface will coexist with many other resources

Because WordPress Plugins & Themes sometimes add notices to every page, you may need to use CSS to hide these to protect the user experience.

From a design perspective, because WordPress has a left-aligned Admin Menu and top-aligned Admin Bar, your available screen real estate is more limited and users are often seeing menus with text and iconography other than yours.