Creating consistent admin interfaces

These guidelines aim to create a cohesive admin interface, focusing on usability, accessibility, and a structured layout. See the table of contents for what is included and to navigate to each section easily.

Note: The terminology used here is defined in the admin interface glossary.

1. Breadcrumbs

Breadcrumbs serve as a navigational tool, aiding users in understanding their current location, content structure, and hierarchy within the admin interface.

Admin > Breadcrumb > Trail
Page title

:art: Design

Structure

  1. Admin: fixed prefix that appears at the beginning of every breadcrumb trail, linking to /admin
  2. Link: opens the page in the same window
  3. Separator: a greater than sign > separates each link

Usage

When to use:

  • Present on every admin page
  • Sit above the content (title, description, tabs)
  • Show the currently selected page

When not to use:

  • When visiting a new or edit route

Content

  • Each item includes a link to its related page
  • Shows the currently selected page

Accessibility

  • A nav element with aria-label="Breadcrumb" wraps an ordered list to provide a navigation landmark
  • Apply aria-current="page" on the last link to indicate it’s the current page
  • For more details, see WAI-ARIA Authoring Practices Breadcrumb Example

:hammer_and_wrench: Implementation

The DBreadcrumbsContainer component must be placed somewhere on the page:

<DBreadcrumbsContainer />

Then, every DBreadcrumbsItem element added to a any component on a route or a child route will be rendered into this container. Each DBreadcrumbsItem has a @label and @path that must be provided:

<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
<DBreadcrumbsItem
  @path="/admin/plugins"
  @label={{i18n "admin.plugins.title"}}
/>
<DBreadcrumbsItem
  @path="/admin/plugins/{{@plugin.name}}"
  @label={{@plugin.nameTitleized}}
/>

How this looks with a visual example, using the Discourse AI plugin:

2. Page header and title

The top section of an admin page, containing the page title, along with optional actions and description.

:art: Design

Structure

  • Page title: Title of the page

  • Page description: Intro or description of what the content covers (optional)

  • Primary action: Page title primary action (optional)

  • Secondary action: Page title secondary action button settings (optional)

Usage and content

  • Page title: Utilize heading level 1 to explain the main subject of the page in sentence case

  • Page description: Supports basic markdown nodes such as _italic_, **bold**, and [link name](url)

  • Primary action: Utilize btn-primary. Don’t include an icon.

  • Secondary action: Utilize btn-default button settings, visible only if a primary action exists. Don’t include an icon.

    :point_right: Be clear with action buttons. For example, use descriptive labels like “Add emoji” instead of just “Add” to reduce ambiguity.

:hammer_and_wrench: Implementation

The DPageHeader component is used here. This accepts arguments for @titleLabel, @descriptionLabel, @learnMoreUrl, and @shouldDisplay. This uses named yields in Ember to provide 3 sections for the content:

  1. breadcrumbs - Any additional DBreadcrumbsItem components for the page should be placed here.
  2. actions - Used to define the buttons to the right of the title. This yields an object called actions which can be used to render Default, Primary, and Danger buttons.
  3. tabs - Used to define the tabs for the page using NavItem components. @hideTabs can be used to remove this part of the header if it’s not needed.

A complete example is below:

<DPageHeader
  @titleLabel="admin.backups.title"
  @descriptionLabel="admin.backups.description"
  @learnMoreUrl="https://meta.discourse.org/t/create-download-and-restore-a-backup-of-your-discourse-database/122710"
>
  <:breadcrumbs>
    <DBreadcrumbsItem
      @path="/admin/backups"
      @label={{i18n "admin.backups.title"}}
    />
  </:breadcrumbs>
  <:actions as |actions|>
    <actions.Primary
      @action={{routeAction "showStartBackupModal"}}
      @title="admin.backups.operations.backup.title"
      @label="admin.backups.operations.backup.label"
      class="admin-backups__start"
    />
  </:actions>
  <:tabs>
    <NavItem
      @route="admin.backups.settings"
      @label="settings"
      class="admin-backups-tabs__settings"
    />
    <NavItem
      @route="admin.backups.index"
      @label="admin.backups.menu.backup_files"
      class="admin-backups-tabs__files"
    />
    <NavItem
      @route="admin.backups.logs"
      @label="admin.backups.menu.logs"
      class="admin-backups-tabs__logs"
    />
    <PluginOutlet @name="downloader" @connectorTagName="div" />
  </:tabs>
</DPageHeader>

Page titles for the browser tab are handled in Ember routes using the titleToken functionality. Every time this is used in a route it adds the token to the end of the browser tab title. Be aware that you must use the DiscourseRoute class to extend your route, not the normal Route from ember for this to work:

titleToken() {
  return I18n.t("admin.backups.title");
}

:point_right: The page header is automatically hidden for /new and /edit paths to support Third-level routes. It can be overridden by using @shouldDisplay argument.

3. Tabs

An optional navigation that provides access to deeper levels of settings or features.

:art: Design

We’re using tabs to switch between different but related views within the same context.

Usage

  • Not used for primary navigation
  • Only active one at a time

:hammer_and_wrench: Implementation

See the Page Header details, the tabs are defined in the DPageHeader component.

:white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square: :white_small_square:

4. Overview/section landing page

Allows users to view the contents of a section, especially when the sidebar is collapsed or on mobile.

:art: Design

Structure
Use three equal column layout using a grid system. On small screens, these columns will stack vertically.

Design and usage

  • Can be accessed through breadcrumbs (Admin > Community > Overview)
  • Each section should have one, except for plugins (which show installed) and reports (only one page)
  • The item has a:
    • name - same as the section link
    • description - a short description of what the page is about
    • icon - same icon used for the sidebar

:hammer_and_wrench: Implementation

Code snippets or link to a topic/GitHub

5. Page content

The main area of an admin page where settings, configurations, and other content are displayed and interacted with.

:art: Design

Structure
Use a 2/3 + 1/3 layout using a grid system. The primary section takes up two-thirds and the secondary section takes up one-third of the space. On small screens, these columns will stack vertically.

  • Config area: A specific section within the page content dedicated to settings and configurations.
  • Help/reference/inset: An area within the page content providing guides, documentation, or additional contextual information. (optional)

Design and usage

  • Group similar settings and actions together in cards
  • Structure primary/secondary layouts so the primary (2/3) section is used for main settings, and the secondary (1/3) section is for additional information or helpful context
  • If the secondary section is not available, keep the primary section width the same

Content

:hammer_and_wrench: Implementation

Code snippets or GitHub links

5.a. Subhead

A subhead is a secondary heading used to break up the content under a section, usually below the tabs.

Structure

  • Subheading: Subheading of what the content covers (optional)
  • Primary action: Subheading primary action (optional)
  • Secondary action: Subheading secondary action button settings (optional)

Usage and content

  • Subheading: Utilize heading level 2 to explain the main subject of the related content. Only include if:

    • There is a primary action button, or
    • There is a description explaining the section.
  • Primary action: Utilize btn-primary. Don’t include an icon.

  • Secondary action: Utilize btn-default button settings, visible only if a primary action exists. Don’t include an icon.

    :point_right: Be clear with action buttons. For example, use descriptive labels like “Add emoji” instead of just “Add” to reduce ambiguity.

:hammer_and_wrench: Implementation

This is similar to DPageHeader, there is an DPageSubheader component. The main difference is that there is only a single named yield for actions.

  1. actions - Used to define the buttons to the right of the title. This yields an object called actions which can be used to render Default, Primary, and Danger buttons.
<DPageSubheader @titleLabel="admin.backups.files_title">
  <:actions>
    <actions.Primary
      @action={{routeAction "showStartBackupModal"}}
      @title="admin.backups.operations.backup.title"
      @label="admin.backups.operations.backup.label"
      class="admin-backups__start"
    />
  </:actions>
</DPageSubheader>

5.b. Config area

The config area is made up of cards or sections. Cards are great for grouping related info and tasks, helping users scan and prioritize content more easily.

:art: Design

Card

Cards are set up with a 2px border-radius and use a background of --secondary. It also have a 1px solid border with --primary-low and 20px padding around the content.

Default variation

Accordion variation

Design and usage

  • Group related information
  • Display information so admins and mods see the most important stuff first
  • Use headings that clearly explain what the card is for
  • Break complicated ones into multiple sections, if necessary

default variation

  • Stick to one primary call to action per card
  • Place primary call to action at the bottom of the card for next steps

accordion variation

  • Use the top right corner of the card for optional actions like “View all”

Content

  • All forms should use the FormKit ember components in core described in the docs
  • Card headers should be sentence-cased
    :white_check_mark: Do :x: Don’t
    General settings General Settings
    Contact information CONTACT INFORMATION

:hammer_and_wrench: Implementation

We have an AdminConfigAreaCard component that should be used for all of these cards. For now this only has @translatedHeading and @heading arguments, in future we can add actions and make them collapsible and so on:

<AdminConfigAreaCard
  @heading="admin.config_areas.about.general_settings"
  class="admin-config-area-about__general-settings-section"
>
  <AdminConfigAreasAboutGeneralSettings
    @generalSettings={{this.generalSettings}}
    @setGlobalSavingStatus={{this.setSavingStatus}}
    @globalSavingStatus={{this.saving}}
  />
</AdminConfigAreaCard>

Embedded site settings

This section is a work in progress.

5.c. Help inset

This section provides additional guidance, documentation, or context within the page content.

v1

:art: Design

Design and usage

  • Show related documentation or guides about the page’s content to provide useful info
  • Include an icon in the heading to make it easily recognizable
  • Place this section in the secondary (1/3) layout area

Content

  • Headers should be sentence-cased

:hammer_and_wrench: Implementation

Code snippets or link to a topic/GitHub

5.d. Table

Tables display information in a grid of cells, columns, and rows, making it easy for admins to quickly scan items and take action.

:art: Design

Usage

  • Use tables to display structured content where each entry shares the same attributes.
  • Allow admins to review, enable/disable, edit, and delete data sets.
  • Suitable for data sets that will continue to grow over time.

Design

  • Use horizontal lines between rows to visually separate content, including the last row. Avoid using borders or frames around the table to prevent it from looking like a net.
  • Do not apply vertical lines between columns. Tables without vertical lines are generally easier to scan and read.

Additional actions

  • Row Actions: Include additional actions in the far-right column of each table row.

    :point_right: Action buttons within table rows can be more minimal since they are placed next to the resource. For instance, “Edit” and “Delete” are sufficient

  • Visibility: Ensure these actions are always visible.
  • Action Menu: If a table row contains more than two actions, consider using a “more menu” button with a dropdown list of options. Icons in the dropdown menus are encouraged to break things up visually.

Content

  • Header: The table header is the top row that identifies the columns below. It provides clarity, especially if the data is non-descriptive or ambiguous. Headers should be short, descriptive, and relevant, using title case. Avoid headers that are too long for the content in the rows below.
  • Columns: Order columns by priority or in a way that tells a coherent story with the data. Size columns according to their content, with narrow columns for small content and wider columns for paragraphs.
  • Rows: Rows should support text, buttons, links, and icons to enhance the data presentation.
  • No data: Empty lists should use the AdminConfigAreaEmptyList component with a CTA button and label to guide the user towards creating new records

:hammer_and_wrench: Implementation

There is a small collection of CSS classes that must be used with tables to make them work well on mobile and desktop.

<table> elements should have the d-admin-table class applied.

<tr> elements should have the d-admin-row__content class applied.

<td> elements containing a lot of descriptive text (usually the leftmost column) should use the d-admin-row__overview class. All other cells should use d-admin-row__detail.

<td> elements which wrap the buttons on each row should have the d-admin-row__controls CSS class applied. This ensures the buttons are aligned. Each button should have the btn-small class applied too.

For mobile, each <td> element except the d-admin-row__overview should also include a <div> with the class d-admin-row__mobile-label, that contains an I18n label that is the same as the one in the <th> for that column:

<td class="d-admin-row__detail">
  <div class="d-admin-row__mobile-label">
    {{i18n "chat.incoming_webhooks.emoji"}}
  </div>
  {{replaceEmoji webhook.emoji}}
</td>

This displays the table row as an easier to read card-based format on mobile:

For [...] dropdown menus, DMenu should be used with DropdownMenu, here is an example:

<DMenu
  @identifier="backup-item-menu"
  @title={{i18n "more_options"}}
  @icon="ellipsis-v"
  class="btn-small"
>
  <:content>
    <DropdownMenu as |dropdown|>
      <dropdown.item>
        <DButton ...[button args here] />
      </dropdown.item>
      <dropdown.item>
        <DButton ...[button args here] />
      </dropdown.item>
    </DropdownMenu>
  </:content>
</DMenu>

Toggles in the table row are handled using the DToggleSwitch component:

<DToggleSwitch
  @state={{this.enabled}}
  class="admin-flag-item__toggle {{@flag.name_key}}"
  {{on "click" (fn this.toggleFlagEnabled @flag)}}
/>

5.e Third-level route

A third-level route is one that can be reached only from a config area. These usually come in the form of edit/new routes like this one for flags:

This is where forms using FormKit will be placed in most cases.

Use the standard RESTful routes for these:

Action Path
New <resource>/new
Edit <resource>/:id/edit

and ensure that the routes are also routed in the back-end. (Reloading the new- or edit page should not result in an error.)

:art: Design

Usage

  • Prefer having these third-level routes over having inline forms on the main route or within a table. Standalone edit and new routes are best, as they can easily be linked to.
  • Do not show the top part of the page UI (breadcrumbs, page header and subheader)
  • Instead, show a single “Back to X” link that allows the admin to get to the main config area
  • The content of the page should be wrapped in at least one AdminConfigAreaCard
  • Any sub-titles on the page should be done with config area cards

:hammer_and_wrench: Implementation

There is a simple BackButton component that can be used on the top of the page to go back:

<BackButton
  @route="adminConfig.flags"
  @label="admin.config_areas.flags.back"
/>

6. General guidance

All text in admin interfaces should follow the text formatting guidelines outlined here:

7. Plugins

Some plugins need an in-depth configuration UI for their plugin (for example AI, Automation, Gamification) rather than only having a collection of site settings. For example, here is Discourse AI:

Some examples of plugins using this are:

:art: Design

Usage

  • General admin UI guidelines should be followed when making independent plugin UIs.

:hammer_and_wrench: Implementation

Ember Routing

  • All route templates will be under
    admin/assets/javascripts/discourse/templates/admin-plugins/show/
  • All route js files will be under admin/assets/javascripts/discourse/routes/ and
    prefixed with admin-plugins-show-
  • The admin route map should be in a file like admin-PLUGIN-NAME-plugin-route-map.js
  • The route map should have a structure like this. The important part is that
    we use admin.adminPlugins.show as the resource.
export default {
  resource: "admin.adminPlugins.show",

  path: "/plugins",

  map() {
    this.route("discourse-ai-personas", { path: "ai-personas" }, function () {
      this.route("new");
      this.route("show", { path: "/:id" });
    });
  },
};
  • The current example of how this all works is seen in the Discourse AI plugin, if you go to /admin/plugins/discourse-ai/ai-personas
  • If you only have a “top level” route, e.g. one that doesn’t define sub-routes, then the template path will be something like admin/assets/javascripts/discourse/templates/admin-plugins/show/your-route-name.hbs. If there are sub routes then you get into the territory of needing index.hbs, show.hbs, and new.hbs routes and so on.

Navigation

Plugins can either show their navigation in an inner sidebar, or on the top tabbed navigation bar. The latter is highly recommended, and in future the inner sidebar support may be dropped.

Server-Side

  • add_admin_route is still used to show the custom admin routes in the admin sidebar and from the /plugins index with the tabs along the top. Basically, this defines the root page of your plugin UI.
    • use_new_show_route: true should be passed as an additional argument here so the new plugin show page is used.

UI Conventions

  • Each index route for the plugin should show a DPageSubheader component to describe the intent of that route and to add any related action buttons.
  • Action buttons that need to be rendered into the main plugin page header must use the admin-plugin-config-page-actions outlet with a dedicated component. The best place to do this is in the same initializer where addAdminPluginConfigurationNav is used.
    • plugin and actions are passed as outletArgs. plugin is the model representation of the current plugin so the name of the plugin and other things can be accessed, actions are the yielded action button components from DPageHeader.
api.renderInOutlet(
  "admin-plugin-config-page-actions",
  ChatAdminPluginActions
);

Related topics:

Last edited by @martin 2024-12-18T07:13:22Z

Last checked by @hugh 2024-09-17T05:50:18Z

Check documentPerform check on document:
7 Likes

And

still don’t work. I think the second one is -23 instead of -24

5 Likes

So glad to finally see this up on meta. Months of work went into this, and we are going to be using it to standardize the UI and navigation of every page in the admin interface.

Should we maybe just remove that table of contents and rely on discotoc instead? I think that would be less fragile, though I do like seeing the table of contents at the top of the post.

7 Likes

Thanks @Moin - all fixed!

I’ve made this change, otherwise it’s simply a duplicated table of contents.

4 Likes

A post was split to a new topic: Display username in browser tab when on user admin