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
Design
Structure
-
Admin: fixed prefix that appears at the beginning of every breadcrumb trail, linking to
/admin
- Link: opens the page in the same window
-
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 witharia-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
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.
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.Be clear with action buttons. For example, use descriptive labels like “Add emoji” instead of just “Add” to reduce ambiguity.
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:
-
breadcrumbs
- Any additionalDBreadcrumbsItem
components for the page should be placed here. -
actions
- Used to define the buttons to the right of the title. This yields an object calledactions
which can be used to renderDefault
,Primary
, andDanger
buttons. -
tabs
- Used to define the tabs for the page usingNavItem
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");
}
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.
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
Implementation
See the Page Header details, the tabs are defined in the DPageHeader
component.
4. Overview/section landing page
Allows users to view the contents of a section, especially when the sidebar is collapsed or on mobile.
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
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.
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
- Follow the guidelines for config area when adding content
- Follow the guidelines for help inset content
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.Be clear with action buttons. For example, use descriptive labels like “Add emoji” instead of just “Add” to reduce ambiguity.
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
.
-
actions
- Used to define the buttons to the right of the title. This yields an object calledactions
which can be used to renderDefault
,Primary
, andDanger
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.
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
Do Don’t General settings General Settings Contact information CONTACT INFORMATION
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
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
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.
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.
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
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.)
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
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:
- Discourse AI GitHub - discourse/discourse-ai
- Discourse Gamification GitHub - discourse/discourse-gamification
- Discourse Chat (core)
Design
Usage
- General admin UI guidelines should be followed when making independent plugin UIs.
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 withadmin-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 useadmin.adminPlugins.show
as theresource
.
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 needingindex.hbs
,show.hbs
, andnew.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.
- Any links that will be shown in either the top bar or the inner sidebar for
the plugin show page should be defined in an initializer (e.g.
assets/javascripts/initializers/admin-plugin-configuration-nav.js
) using
api.addAdminPluginConfigurationNav
and specifying the mode with either
PLUGIN_NAV_MODE_TOP
orPLUGIN_NAV_MODE_SIDEBAR
. - This initializer should only run if the user is admin.
- The site settings link for the plugin is generated automatically, no need to include it here.
- An example can be seen here discourse-ai/assets/javascripts/initializers/admin-plugin-configuration-nav.js at ab4544d8977ec0e9d6aa42b4551df8317aa9b365 · discourse/discourse-ai · GitHub.
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 whereaddAdminPluginConfigurationNav
is used.-
plugin
andactions
are passed asoutletArgs
.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 fromDPageHeader
.
-
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 document
Perform check on document: