Repository navigation
Systematising typography styles in the admin UI #64340
Description
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:
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.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
- StatusShow more project fields💫 Done
Activity
[-]Font sizes[/-][+]Font styles[/+][-]Font styles[/-][+]Systematising typography styles in the admin UI[/+]jameskoster commentedon Aug 16, 2024
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
11px12px13px15px🆕20px🆕32px🆕These sizes largely align with the current design, with a few new additions to enhance flexibility.
Line Heights
16px20px24px🆕28px🆕32px🆕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.Font Weights
400500Font 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
-apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif-apple-system, "system-ui", "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serifMenlo, 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 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 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.
jasmussen commentedon Aug 19, 2024
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.
jameskoster commentedon Sep 18, 2024
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.:
The js is a bit more complicated due to the existing
TextandHeadingcomponents, 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
TextandHeadingwould support predefined styles that match (or utilise, if that's possible) the scss mixins. Consumers could do something like:Which would output a span that is styled according to the properties of the
body-mediummixin.Headingwould 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 useheading-xl– but it should also be possible to apply different styles to different levels, e.g.:This example would output a
<h1>with theheading-smallstyle 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.
mirka commentedon Sep 19, 2024
Sounds good to me. We can't use the
styleprop because that clashes with a standard React prop, butvariantorpresetcould do. The preset can be overridable through more specific props likeweightandsize. We can also anticipate that presets will eventually be fully customizable through theming.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.
ciampo commentedon Sep 19, 2024
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 ?Had the same thinking. Also, I'd be tempted to remove truncation features — if consumers need truncation, maybe they can compose the elements themselves?
mirka commentedon Sep 19, 2024
Yes, I guess that would be simpler, given how rare it should be to stray from the presets.
2 remaining items
karmatosed commentedon May 31, 2025
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'.
jameskoster commentedon Mar 19, 2026
The recently-merged Text component (and associated tokens in the
@wordpress/themepackage) should provide the foundations for this.aduth commentedon Mar 26, 2026
@jameskoster Can you clarify, from your perspective, what remains in this issue in order for it to be considered completed?
jameskoster commentedon Mar 30, 2026
I guess we need to utilise those tools in all areas of the UI. We still have many instances of the 'legacy'
Textcomponent 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.ciampo commentedon Mar 30, 2026
If we feel that the current
Textsatisfies 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 newText, similarly to what we're doing for dataviews in #76100aduth commentedon Mar 31, 2026
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?
jameskoster commentedon Apr 1, 2026
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:
aduth commentedon Apr 1, 2026
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 😅
jameskoster commentedon Apr 2, 2026
I analysed the
edit-siteandblock-editorpackages for candidates:edit-site
Existing
__experimentalTextusage to replaceText variant="muted"for totals and page indicatorsStrong candidates for
Textadoption1. 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.jssrc/components/welcome-guide/page.jssrc/components/welcome-guide/template.jsAll use
edit-site-welcome-guide__heading/__textclasses with SCSS for font-family, font-size, line-height. These could useHeading+Textto 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 namesrc/components/page-templates/fields.js— duplicates the author name<span>patternThese are typographic UI labels in data views.
Textwith variants would unify styling and optionally handle truncation.3. Site hub title
src/components/site-hub/index.js— Site title inside aButton, styled via.edit-site-site-hub__titleSCSS (font-size 15px, weight, color, plus manualoverflow: hidden/text-overflow: ellipsis). ATextwith a truncation prop could replace the CSS-only truncation.4. Save panel copy
src/components/save-panel/index.js—<p>wrappingsprintftheme-activation text. A standalone paragraph in a modal flow that could useText as="p".5. Sidebar navigation screen description
src/components/sidebar-navigation-screen/index.js— Thedescriptionprop is rendered in a plain<div className="...description">. This is supporting copy that would be more semantically appropriate asText. Fixing it here would cascade to all routes using this screen (stylebook, main navigation, etc.).block-editor
## Existing `__experimentalText` / `Heading` usage to replaceThe package already uses the 'legacy' primitives in several places:
block-card/index.js—Textfor block descriptionblock-switcher/index.js—Textfor bindings hintblock-patterns-paging/index.js—Text variant="muted"for counts/pageinspector-popover-header/index.js—Heading+Textfor helpblock-bindings/attribute-control.js—Text truncate/variant="muted"block-styles/menu-items.js—Text as="span"withtruncate/limitblock-allowed-blocks/modal.js—Textfor intro copyinserter/block-patterns-tab/pattern-category-previews.js— MixedHeading+TextStrong 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 asText variant="muted"orText as="p":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:
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.jsusesText variant="muted"for its "No results found" — the same message, styled differently.3. Raw headings (
<h2>)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 wrapsTruncate)4.
<div>used for titles / help (no semantic element)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 adiv)5.
<span>for typographic labelssrc/components/link-control/link-preview.js<span className="…__preview-info">+ error text spansrc/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 bothsrc/components/duotone-control/index.jsandsrc/components/global-styles/filters-panel.js— good centralisation +Textcandidate.Truncation patterns (three competing strategies)
This is a notable area of inconsistency:
Strategy 1 —
Truncatecomponent: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.jsStrategy 2 —
Textwithtruncateprop:Used in
block-bindings/attribute-control.js,block-styles/menu-items.jsStrategy 3 — Manual
slice+'...':Used in
block-title/use-block-display-title.js(lines 71–79) whenmaximumLengthis setIf
Textgains full parity withTruncate(numberOfLines,ellipsizeMode), these could all converge on a single approach.Key inconsistencies
<p>,<span>,<div>, andText variant="muted"are all used for the same "no results" message pattern. Could be replaced withEmptyStateso potentially out of scope.components-base-control__helpon<p>, custom…helptextclasses, andTextelsewhere<div>+ BEM__item-title, sometimesHeading/Textin newer inserter UI<h2>in older components vsHeadingprimitive in newer onesPerhaps 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-siteandblock-editorUI text to shared primitivesAudit of inline text rendering patterns across
edit-siteandblock-editorthat should migrate toText.Migrate existing
__experimentalText/__experimentalHeadingto@wordpress/uiTextedit-siteadd-new-template/add-custom-template-modal-content.jsusesTextfor suggestion titles, no-results, instructional paragraphs, button labels.add-new-template/index.jsusesTextfor template titles.pagination/index.jsusesText variant="muted"for totals and page indicator.add-new-template-legacy/*mirrors the above.block-editorblock-card/index.jsusesTextfor block description.block-switcher/index.jsusesTextinsideMenuGroup.block-patterns-paging/index.jsusesText variant="muted"for counts/page.inspector-popover-header/index.jsusesHeading+Text.hooks/block-bindings.jsusesText as="div" variant="muted".block-bindings/attribute-control.jsusesText truncate/variant="muted".block-styles/menu-items.jsusesText as="span"withtruncate/limit.block-toolbar/pattern-overrides-dropdown.jsusesTextfor description.block-allowed-blocks/modal.jsusesTextfor intro copy.inserter/block-patterns-tab/pattern-category-previews.jsusesHeading+Text.global-styles/shadow-panel-components.jsusesHeading level={5}.sidebar-navigation-screen/index.jsuses__experimentalHeadingwithsize,level, and inlinecolor.Text component migrations
edit-siteeditor.js,page.js,template.jsuse raw<h1>+<p>with BEM classes (edit-site-welcome-guide__heading/__text). Migrate toHeading+Text.save-panel/index.jsuses<p>wrappingsprintftheme-activation text. Migrate toText as="p".sidebar-navigation-screen/index.jsrenders thedescriptionprop in a<div>. Migrate toText. Fixes all routes using this screen (stylebook, main nav, etc.).page-patterns/fields.jsuses<span>with class-based sync status styling. Migrate toTextwith appropriate variant.page-patterns/fields.jsuses<span className="fields-controls__author-name">. Migrate toTextwith truncation.page-templates/fields.jsduplicates the author name<span>pattern. Migrate toTextwith truncation.site-hub/index.jsstyles the site title via SCSS (font-size, weight, color, manual ellipsis). Migrate toTextwith truncation prop.block-editorlayouts/constrained.jsuses<p className="…__layout-constrained-helptext">. Migrate toText.layouts/grid.jsuses<p className="components-base-control__help">. Migrate toText.hooks/block-hooks.jsuses<p className="…__block-hooks-helptext">. Migrate toText.hooks/list-view.jsuses<p className="…__no-blocks">. Migrate toText.duotone-control/index.jsuses plain<p>for explainer. Migrate toText. (Duplicated inglobal-styles/filters-panel.js— centralise.)global-styles/filters-panel.jsduplicates the same<p>explanation. Migrate toTextand share with duotone control.link-control/index.jsuses<p className="…__help">. Migrate toText as="p"(preserveidforaria-describedby).inserter/media-tab/media-preview.jsuses two<p>elements for external-image warning. Migrate toText.block-visibility/modal.jsuses multiple<p className="…__description">withcreateInterpolateElement+<strong>. Migrate toText.block-removal-warning-modal/index.jsuses<p>+<strong>for warning copy. Migrate toText.block-navigation/dropdown.jsuses<p className="…__label">. Migrate toText.warning/index.jsuses<p className="…__message">. Migrate toText.text-indent-control/index.jsuses<p className="components-base-control__help">. Migrate toText.url-popover/image-url-input-ui.jsuses<p>+<p className="description">. Migrate toText.link-control/link-preview.jsuses<span className="…__preview-info">+ error span. Migrate toTextwith appropriate variant.block-card/index.jsuses<span className="…__name">. Migrate toText.block-variation-picker/index.jsuses<span className="…__variation-label">. Migrate toText.media-replace-flow/index.jsuses<span className="…__image-url-label">. Migrate toText.hooks/block-fields/link/index.jsuses<span className="…__link-title">(long URLs — truncation candidate). Migrate toTextwith truncation.hooks/block-fields/media/index.jsuses<span className="…__media-title">(has existing TODO about truncation). Migrate toTextwith truncation.multi-selection-inspector/index.jsuses<div className="…__card-title">. Migrate toTextorHeading.block-patterns-list/index.jsuses<div className="…__item-title">. Migrate toText.block-switcher/pattern-transformations-menu.jsuses<div className="…__item-title">. Migrate toText.block-pattern-setup/index.jsuses<div className="…__item-title">. Migrate toText.inserter/block-patterns-tab/patterns-filter.jsuses<div className="…__patterns-filter-help">for a body paragraph. Migrate toText as="p".Heading component migrations
block-editorblock-compare/block-view.jsuses<h2 className="…__heading">. Migrate toHeading.inserter/panel.jsuses<h2 className="…__panel-title">. Migrate toHeading.hooks/block-fields/index.jsuses<h2 className="…__header-title">(one branch wrapsTruncate). Migrate toHeading.I note we'd be blocked until the UI package is available in these contexts, and we'd likely need to introduce a
truncateproperty toText. WDYT?ciampo commentedon Apr 10, 2026
I guess we can keep #76929 specifically to
@wordpress/uiand this issue for the rest of the repo?jameskoster commentedon Apr 13, 2026
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.