Skip to content

Systematising typography styles in the admin UI #64340

Closed

Description

@jameskoster
type

There are several typographic styles used throughout the editor, including:

  • Body text at 13px
  • Help text at 12px
  • Labels at 11px, all-caps

However, these styles are not globally registered, leading to inconsistent and ad hoc usage across the interface. For example, in the data views pagination control, the 'label' styling is applied ad hoc to the 'of 3' suffix here:

Screenshot 2024-08-07 at 15 52 22

This means that if the label style is updated in the future, this suffix will not automatically align with the preceding label leading the UI breakage.

Another problem is unique type styles, such as the section headings in the Editor Preferences modal. This is an inevitable consequence of using such a small / rigid scale and leads to inconsistency and fragility in the overall design. Some of these pain points are already felt in Data Views as mentioned above.

Establishing beautiful and consistent typographic rules will be increasingly important as the admin redesign progress and we begin building UIs outside of the Editor interface (settings forms and so on).

Solution

To ensure design consistency and ease of maintenance, we should establish a set of typographic styles that can be utilized throughout the admin interface.

Goals

  • Develop a comprehensive set of typographic styles.
  • Define high-level heuristics like how weight is applied.
  • Ensure that all typographic elements in the UI adhere to these styles.
  • Facilitate easier updates and maintenance of typographic consistency.

Let's discuss which type styles we'll need keeping in mind high-density UI's like the block inspector, but also admin pages like settings screens where there can be more breathing room. As we explore properties, ideally line-height values should be multiples of 4 to align with the 4px baseline.

Activity

  1. changed the title [-]Font sizes[/-] [+]Font styles[/+] on Aug 7, 2024
  2. changed the title [-]Font styles[/-] [+]Systematising typography styles in the admin UI[/+] on Aug 9, 2024
  3. jameskoster commented on Aug 16, 2024

    @jameskoster
    ContributorAuthor

    Typography Tokens

    Tokenizing typographical properties allows for streamlined, future-proof adjustments while ensuring that any ad-hoc customizations remain consistent with the established system. Here’s a breakdown of the tokens I've been exploring:

    Font Sizes

    • XS: 11px
    • S: 12px
    • M: 13px
    • L: 15px 🆕
    • XL: 20px 🆕
    • 2XL: 32px 🆕

    These sizes largely align with the current design, with a few new additions to enhance flexibility.

    Line Heights

    • XS: 16px
    • S: 20px
    • M: 24px 🆕
    • L: 28px 🆕
    • XL: 32px 🆕
    • 2XL: 40px 🆕

    Line-height application is quite inconsistent across the UI. By standardizing line-height values as multiples of 4px, we ensure text elements align seamlessly with the 4px vertical rhythm established in many components. This alignment is crucial for creating a visually cohesive interface, especially when placing text next to elements like buttons and inputs.

    Screenshot 2024-08-16 at 15 02 24

    Font Weights

    • Regular: 400
    • Semi-bold: 500

    Font weight usage has also been inconsistent, with identical headings sometimes utilising different weights. Simplifying this to just two weights—Regular for body text and Semi-bold for headings—ensures a more uniform and polished appearance.

    Font Families

    • Headings: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif
    • Body: -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif
    • Mono: Menlo, Consolas, monaco, monospace;

    These font families are based on the current system, now divided between headings and body text. In the future, platform personalization options might allow users to customize fonts alongside color schemes and other visual elements. By tokenising families we can hopefully make this easier to implement.

    Text Styles

    By combining these tokens, we can create a set of text styles that can be applied to headings, body text, and other UI elements.

    Headings

    headings

    Headings serve to organize content within the admin interface, enabling users to quickly scan and navigate. While typically applied to heading elements, these styles can also be used for other purposes, such as input labels or tabs. In denser UIs, headings may share the same size as body text, so weight is used to distinguish between them.

    Body Text

    body

    Body text styles are used for any text not specifically intended for navigation or section separation. This includes paragraphs, input fields, tooltips, and more.

    Contextual Mockups

    To illustrate these styles in action, I've created a series of mockups. These sketches demonstrate how the existing editor UI can largely retain its current appearance while achieving greater consistency in other areas of the admin as we work on redesigning them.

    Examples
  4. jasmussen commented on Aug 19, 2024

    @jasmussen
    Contributor

    Looks good, and will be excellent documentation for what's de-facto shipping. I don't know that this necessarily needs a ton of design feedback, as despite the sound of this, there's practically very little news here, all of this is shipping. Which is to say, it'll be good to add this documentation to Storybook. Any changes we make to font sizes or type scales in the future, we can simply update the documentation along with it.

  5. jameskoster commented on Sep 18, 2024

    @jameskoster
    ContributorAuthor

    I've started looking into the implementation for this one. The css/scss side of things seems straight-forward; we can create variables for each token, and mixins for the different text styles, e.g.:

    @mixin heading-small() {
    	font-family: $font-family-headings;
    	font-weight: $font-weight-semi-bold;
    	font-size: $font-size-x-small;
    	line-height: $line-height-x-small;
    }
    

    The js is a bit more complicated due to the existing Text and Heading components, which include logic for calculating font size and other details. This logic is not fully aligned with the scale we're looking to implement so will need to be revised, removed, or new component(s) created. Let's discuss how we expect things to work so we can investigate the path forwards.

    To align with the css, my thinking is that Text and Heading would support predefined styles that match (or utilise, if that's possible) the scss mixins. Consumers could do something like:

    <Text style="body-medium">Code is Poetry</Text>
    

    Which would output a span that is styled according to the properties of the body-medium mixin.

    Heading would work similarly, but needs to entertain different levels (h1, h2, h3, and so on). Levels can refer to certain styles by default – for instance level 1 might use heading-xl – but it should also be possible to apply different styles to different levels, e.g.:

    <Heading level="1" style="heading-small">Code is Poetry</Heading>
    

    This example would output a <h1> with the heading-small style properties.

    Finally, though niche, it should be possible to not use a preset style at all. Consumers should have the option to create custom styles, using either the supplied tokens, hard-coded values, or a mixture thereof.

    cc @WordPress/gutenberg-components for thoughts.

  6. mirka commented on Sep 19, 2024

    @mirka
    Member

    To align with the css, my thinking is that Text and Heading would support predefined styles that match (or utilise, if that's possible) the scss mixins. Consumers could do something like:

    Sounds good to me. We can't use the style prop because that clashes with a standard React prop, but variant or preset could do. The preset can be overridable through more specific props like weight and size. We can also anticipate that presets will eventually be fully customizable through theming.

    The js is a bit more complicated due to the existing Text and Heading components, which include logic for calculating font size and other details. This logic is not fully aligned with the scale we're looking to implement so will need to be revised, removed, or new component(s) created.

    Right, a lot of the current props (not just style-related) are unused and don't align with our current needs, so we need to assess whether we can salvage the current component or we should start a second version from scratch.

  7. ciampo commented on Sep 19, 2024

    @ciampo
    Contributor

    The preset can be overridable through more specific props like weight and size

    I was wondering if those are needed — if we allow folks to specify variant={ undefined }, they could just bring their own CSS, without a need for extra props. And the CSS could refer to individual theme tokens, if they wanted to mix & match preset value with fully custom values. What do you think, @mirka ?

    Right, a lot of the current props (not just style-related) are unused and don't align with our current needs, so we need to assess whether we can salvage the current component or we should start a second version from scratch.

    Had the same thinking. Also, I'd be tempted to remove truncation features — if consumers need truncation, maybe they can compose the elements themselves?

  8. mirka commented on Sep 19, 2024

    @mirka
    Member

    I was wondering if those are needed — if we allow folks to specify variant={ undefined }, they could just bring their own CSS, without a need for extra props. And the CSS could refer to individual theme tokens, if they wanted to mix & match preset value with fully custom values. What do you think, @mirka ?

    Yes, I guess that would be simpler, given how rare it should be to stray from the presets.

  9. added
    Design SystemIssues related to the system of combining components according to best practices.
    on Oct 8, 2024
  10. 2 remaining items

  11. karmatosed commented on May 31, 2025

    @karmatosed
    Member

    For now, it looks like this needs feedback over a design so let's get that focus going on by triaging down to one design type label of 'needs design feedback'.

  12. jameskoster commented on Mar 19, 2026

    @jameskoster
    ContributorAuthor

    The recently-merged Text component (and associated tokens in the @wordpress/theme package) should provide the foundations for this.

  13. aduth commented on Mar 26, 2026

    @aduth
    Member

    The recently-merged Text component (and associated tokens in the @wordpress/theme package) should provide the foundations for this.

    @jameskoster Can you clarify, from your perspective, what remains in this issue in order for it to be considered completed?

  14. jameskoster commented on Mar 30, 2026

    @jameskoster
    ContributorAuthor

    I guess we need to utilise those tools in all areas of the UI. We still have many instances of the 'legacy' Text component for example. I suppose that's a very broad scope though. I wouldn't be opposed to closing this and creating more targeted issues if y'all prefer.

  15. ciampo commented on Mar 30, 2026

    @ciampo
    Contributor

    If we feel that the current Text satisfies most needs in terms of making all necessary variants available, we could then consider opening one or more issues to track the adoption of the new Text, similarly to what we're doing for dataviews in #76100

  16. aduth commented on Mar 31, 2026

    @aduth
    Member

    I created a tracking issue for the more obvious component adaptations in #76929. @jameskoster @ciampo do you think that'll suffice for next steps here?

  17. jameskoster commented on Apr 1, 2026

    @jameskoster
    ContributorAuthor

    Ah, I didn't mean only the UI package. All text in Gutenberg outside of primitive components will need to be updated as well. For example the title and description in the sidebar:

    Image
  18. aduth commented on Apr 1, 2026

    @aduth
    Member

    I touched on the broader potential scope in #76929 ("there's a long tail of components which could be updated to use the Text component"). Practically speaking, how would you suggest going about tracking that? Is it a definable scope? Do we want to focus on specific elements (e.g. the sidebar you mentioned)? I'm just wary of an issue where the work and close condition is unclear 😅

  19. jameskoster commented on Apr 2, 2026

    @jameskoster
    ContributorAuthor

    I analysed the edit-site and block-editor packages for candidates:

    edit-site

    Existing __experimentalText usage to replace

    • Add New Template modals — suggestion rows, no-results copy, instructional paragraphs
    • Template list items — template titles
    • Pagination — Text variant="muted" for totals and page indicators

    Strong candidates for Text adoption

    1. Welcome guide trio (headings + body copy)

    Three files repeat the same raw <h1> + <p> pattern with BEM classes for typography:

    • src/components/welcome-guide/editor.js
    • src/components/welcome-guide/page.js
    • src/components/welcome-guide/template.js

    All use edit-site-welcome-guide__heading / __text classes with SCSS for font-family, font-size, line-height. These could use Heading + Text to match the sidebar navigation screen approach.

    2. Data view cells — sync status + author names

    • src/components/page-patterns/fields.js — <span> with class-based sync status styling and author name
    • src/components/page-templates/fields.js — duplicates the author name <span> pattern

    These are typographic UI labels in data views. Text with variants would unify styling and optionally handle truncation.

    3. Site hub title

    • src/components/site-hub/index.js — Site title inside a Button, styled via .edit-site-site-hub__title SCSS (font-size 15px, weight, color, plus manual overflow: hidden / text-overflow: ellipsis). A Text with a truncation prop could replace the CSS-only truncation.

    4. Save panel copy

    • src/components/save-panel/index.js — <p> wrapping sprintf theme-activation text. A standalone paragraph in a modal flow that could use Text as="p".

    5. Sidebar navigation screen description

    • src/components/sidebar-navigation-screen/index.js — The description prop is rendered in a plain <div className="...description">. This is supporting copy that would be more semantically appropriate as Text. Fixing it here would cascade to all routes using this screen (stylebook, main navigation, etc.).
    block-editor ## Existing `__experimentalText` / `Heading` usage to replace

    The package already uses the 'legacy' primitives in several places:

    • block-card/index.js — Text for block description
    • block-switcher/index.js — Text for bindings hint
    • block-patterns-paging/index.js — Text variant="muted" for counts/page
    • inspector-popover-header/index.js — Heading + Text for help
    • block-bindings/attribute-control.js — Text truncate / variant="muted"
    • block-styles/menu-items.js — Text as="span" with truncate / limit
    • block-allowed-blocks/modal.js — Text for intro copy
    • inserter/block-patterns-tab/pattern-category-previews.js — Mixed Heading + Text

    Strong candidates

    1. Help / description paragraphs (<p> with BEM classes)

    A common pattern across the package — raw <p> elements with custom classes for typographic styling, doing the same job as Text variant="muted" or Text as="p":

    File Pattern
    src/layouts/constrained.js <p className="…__layout-constrained-helptext">
    src/layouts/grid.js <p className="components-base-control__help">
    src/hooks/block-hooks.js <p className="…__block-hooks-helptext">
    src/hooks/list-view.js <p className="…__no-blocks">
    src/components/link-control/index.js <p className="…__help">
    src/components/text-indent-control/index.js <p className="components-base-control__help">
    src/components/warning/index.js <p className="…__message">
    src/components/url-popover/image-url-input-ui.js <p> + <p className="description">

    2. Empty state messages

    Several "no results" messages use inconsistent patterns:

    File Pattern
    src/components/inserter/no-results.js <p>{ __( 'No results found.' ) }</p>
    src/components/block-switcher/index.js <p className="…__no-transforms">
    src/components/block-manager/index.js <p className="…__no-results">
    src/components/inserter/preview-panel.js <div className="…__preview-content-missing">
    src/components/block-inspector/index.js <span className="…__no-blocks">

    Meanwhile pattern-category-previews.js uses Text variant="muted" for its "No results found" — the same message, styled differently.

    3. Raw headings (<h2>)

    File Pattern
    src/components/block-compare/block-view.js <h2 className="…__heading">
    src/components/inserter/panel.js <h2 className="…__panel-title">
    src/hooks/block-fields/index.js <h2 className="…__header-title"> (one branch wraps Truncate)

    4. <div> used for titles / help (no semantic element)

    File Pattern
    src/components/multi-selection-inspector/index.js <div className="…__card-title">
    src/components/block-patterns-list/index.js <div className="…__item-title">
    src/components/block-switcher/pattern-transformations-menu.js <div className="…-list__item-title">
    src/components/block-pattern-setup/index.js <div className="…__item-title">
    src/components/inserter/block-patterns-tab/patterns-filter.js <div className="…__patterns-filter-help"> (body paragraph in a div)

    5. <span> for typographic labels

    File Pattern
    src/components/link-control/link-preview.js <span className="…__preview-info"> + error text span
    src/components/block-card/index.js <span className="…__name">
    src/components/block-variation-picker/index.js <span className="…__variation-label">
    src/components/media-replace-flow/index.js <span className="…__image-url-label">
    src/hooks/block-fields/link/index.js <span className="…__link-title"> (long URLs — truncation candidate)
    src/hooks/block-fields/media/index.js <span className="…__media-title"> (has an explicit TODO about truncation)

    6. Duplicate duotone explanation

    The same explanatory paragraph appears as a raw <p> in both src/components/duotone-control/index.js and src/components/global-styles/filters-panel.js — good centralisation + Text candidate.


    Truncation patterns (three competing strategies)

    This is a notable area of inconsistency:

    Strategy 1 — Truncate component:
    Used in list-view/block-select-button.js, list-view/drop-indicator.js, inserter-list-item/index.js, link-control/link-preview.js, block-fields/index.js, background-image-control/index.js, block-styles/index.js, block-quick-navigation/index.js

    Strategy 2 — Text with truncate prop:
    Used in block-bindings/attribute-control.js, block-styles/menu-items.js

    Strategy 3 — Manual slice + '...':
    Used in block-title/use-block-display-title.js (lines 71–79) when maximumLength is set

    If Text gains full parity with Truncate (numberOfLines, ellipsizeMode), these could all converge on a single approach.


    Key inconsistencies

    1. Empty states — <p>, <span>, <div>, and Text variant="muted" are all used for the same "no results" message pattern. Could be replaced with EmptyState so potentially out of scope.
    2. Help text — Mix of components-base-control__help on <p>, custom …helptext classes, and Text elsewhere
    3. Pattern/block titles — Sometimes <div> + BEM __item-title, sometimes Heading/Text in newer inserter UI
    4. Truncation — Three different strategies coexisting for the same UX need
    5. Section headings — Raw <h2> in older components vs Heading primitive in newer ones

    Perhaps we should formulate these into a todo list and update the OP? That would look something like this:

    Todo list Here's the tracking issue content:

    Migrate edit-site and block-editor UI text to shared primitives

    Audit of inline text rendering patterns across edit-site and block-editor that should migrate to Text.

    Migrate existing __experimentalText / __experimentalHeading to @wordpress/ui Text

    edit-site

    • Add template suggestion rows — add-new-template/add-custom-template-modal-content.js uses Text for suggestion titles, no-results, instructional paragraphs, button labels.
    • Add template list item — add-new-template/index.js uses Text for template titles.
    • Pagination — pagination/index.js uses Text variant="muted" for totals and page indicator.
    • Legacy add template — add-new-template-legacy/* mirrors the above.

    block-editor

    • Block card description — block-card/index.js uses Text for block description.
    • Block switcher bindings hint — block-switcher/index.js uses Text inside MenuGroup.
    • Block patterns paging — block-patterns-paging/index.js uses Text variant="muted" for counts/page.
    • Inspector popover header — inspector-popover-header/index.js uses Heading + Text.
    • Block bindings — hooks/block-bindings.js uses Text as="div" variant="muted".
    • Attribute control — block-bindings/attribute-control.js uses Text truncate / variant="muted".
    • Block styles menu items — block-styles/menu-items.js uses Text as="span" with truncate / limit.
    • Pattern overrides dropdown — block-toolbar/pattern-overrides-dropdown.js uses Text for description.
    • Allowed blocks modal — block-allowed-blocks/modal.js uses Text for intro copy.
    • Pattern category previews — inserter/block-patterns-tab/pattern-category-previews.js uses Heading + Text.
    • Shadow panel — global-styles/shadow-panel-components.js uses Heading level={5}.
    • Sidebar navigation screen title — sidebar-navigation-screen/index.js uses __experimentalHeading with size, level, and inline color.

    Text component migrations

    edit-site

    • Welcome guide headings + body copy — editor.js, page.js, template.js use raw <h1> + <p> with BEM classes (edit-site-welcome-guide__heading / __text). Migrate to Heading + Text.
    • Save panel copy — save-panel/index.js uses <p> wrapping sprintf theme-activation text. Migrate to Text as="p".
    • Sidebar navigation screen description — sidebar-navigation-screen/index.js renders the description prop in a <div>. Migrate to Text. Fixes all routes using this screen (stylebook, main nav, etc.).
    • Pattern sync status label — page-patterns/fields.js uses <span> with class-based sync status styling. Migrate to Text with appropriate variant.
    • Pattern author name — page-patterns/fields.js uses <span className="fields-controls__author-name">. Migrate to Text with truncation.
    • Template author name — page-templates/fields.js duplicates the author name <span> pattern. Migrate to Text with truncation.
    • Site hub title — site-hub/index.js styles the site title via SCSS (font-size, weight, color, manual ellipsis). Migrate to Text with truncation prop.

    block-editor

    • Layout constrained helptext — layouts/constrained.js uses <p className="…__layout-constrained-helptext">. Migrate to Text.
    • Grid layout help — layouts/grid.js uses <p className="components-base-control__help">. Migrate to Text.
    • Block hooks helptext — hooks/block-hooks.js uses <p className="…__block-hooks-helptext">. Migrate to Text.
    • List view no blocks message — hooks/list-view.js uses <p className="…__no-blocks">. Migrate to Text.
    • Duotone control description — duotone-control/index.js uses plain <p> for explainer. Migrate to Text. (Duplicated in global-styles/filters-panel.js — centralise.)
    • Duotone filters panel description — global-styles/filters-panel.js duplicates the same <p> explanation. Migrate to Text and share with duotone control.
    • Link control help — link-control/index.js uses <p className="…__help">. Migrate to Text as="p" (preserve id for aria-describedby).
    • Media preview modal copy — inserter/media-tab/media-preview.js uses two <p> elements for external-image warning. Migrate to Text.
    • Block visibility modal descriptions — block-visibility/modal.js uses multiple <p className="…__description"> with createInterpolateElement + <strong>. Migrate to Text.
    • Block removal warning modal — block-removal-warning-modal/index.js uses <p> + <strong> for warning copy. Migrate to Text.
    • Block navigation label — block-navigation/dropdown.js uses <p className="…__label">. Migrate to Text.
    • Warning message — warning/index.js uses <p className="…__message">. Migrate to Text.
    • Text indent control help — text-indent-control/index.js uses <p className="components-base-control__help">. Migrate to Text.
    • URL popover description — url-popover/image-url-input-ui.js uses <p> + <p className="description">. Migrate to Text.
    • Link preview info — link-control/link-preview.js uses <span className="…__preview-info"> + error span. Migrate to Text with appropriate variant.
    • Block card name — block-card/index.js uses <span className="…__name">. Migrate to Text.
    • Variation picker label — block-variation-picker/index.js uses <span className="…__variation-label">. Migrate to Text.
    • Media replace flow label — media-replace-flow/index.js uses <span className="…__image-url-label">. Migrate to Text.
    • Block fields link title — hooks/block-fields/link/index.js uses <span className="…__link-title"> (long URLs — truncation candidate). Migrate to Text with truncation.
    • Block fields media title — hooks/block-fields/media/index.js uses <span className="…__media-title"> (has existing TODO about truncation). Migrate to Text with truncation.
    • Multi-selection card title — multi-selection-inspector/index.js uses <div className="…__card-title">. Migrate to Text or Heading.
    • Pattern list item titles — block-patterns-list/index.js uses <div className="…__item-title">. Migrate to Text.
    • Pattern transformations title — block-switcher/pattern-transformations-menu.js uses <div className="…__item-title">. Migrate to Text.
    • Pattern setup title — block-pattern-setup/index.js uses <div className="…__item-title">. Migrate to Text.
    • Patterns filter help — inserter/block-patterns-tab/patterns-filter.js uses <div className="…__patterns-filter-help"> for a body paragraph. Migrate to Text as="p".

    Heading component migrations

    block-editor

    • Block compare heading — block-compare/block-view.js uses <h2 className="…__heading">. Migrate to Heading.
    • Inserter panel title — inserter/panel.js uses <h2 className="…__panel-title">. Migrate to Heading.
    • Block fields header title — hooks/block-fields/index.js uses <h2 className="…__header-title"> (one branch wraps Truncate). Migrate to Heading.

    I note we'd be blocked until the UI package is available in these contexts, and we'd likely need to introduce a truncate property to Text. WDYT?

  20. ciampo commented on Apr 10, 2026

    @ciampo
    Contributor

    I guess we can keep #76929 specifically to @wordpress/ui and this issue for the rest of the repo?

  21. jameskoster commented on Apr 13, 2026

    @jameskoster
    ContributorAuthor

    Actually since this issue mostly describes adding the necessary tools (which now exist), I'll probably close and open a separate issue for updating the editor and admin UI.

  22. moved this from 🏗️ In Progress to 💫 Done in Design systems — Backlogon Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design SystemIssues related to the system of combining components according to best practices.General InterfaceParts of the UI which don't fall neatly under other labels.Needs Design FeedbackNeeds general design feedback.[Focus] Accessibility (a11y)Changes that impact accessibility and need corresponding review (e.g. markup changes).

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions