ALMA Web UI Guidelines

v3.4.1   ·   08-Sep-2023   ·   UI Guidelines 2.1.1

Introduction

This document describes the UI Guidelines for ALMA Web applications; it is itself a Bootstrap-based Angular web application that applies those guidelines.

If you are interested in porting an Angular web application from Semantic UI to Bootstrap, you can read through our migration guide.

Getting started

To develop, or convert, an Angular application that follows these guidelines you'll need to make sure you use the right styling, fonts and icons.

Styles

Add a dependency to @almaobservatory/ui-guidelines-styles to your application. It will bring in Bootstrap as well:
npm install @almaobservatory/ui-guidelines-styles

Add the following line at the very top of your styles.css file to import the CSS definitions:
@import "~@almaobservatory/ui-guidelines-styles/alma-ui-guidelines.css";

NOTE In versions prior to 2.0.0, it was ui-guidelines-styles/dist/... instead of ui-guidelines-styles/....

Fonts

These guidelines recommend using the Lato font and hosting it locally.

NOTE Hosting Lato instead of loading it from some CDN ensures the font is available to the application even if the CDN is down or not reachable.

  • Download and expand the Lato archives from here
  • Create directory src/assets/fonts
  • Copy files lato-vNN-latin-regular.woff and lato-vNN-latin-regular.woff2 from the expanded archive to src/assets/font
  • Add the following definition to your styles.css file to define the new font; the example is for version 23 of the font:

    @font-face {
        font-family: 'Lato';
        font-style: normal;
        font-weight: 400;
        src: local(''),
        url('assets/fonts/lato-v23-latin-regular.woff2') format('woff2'),
        url('assets/fonts/lato-v23-latin-regular.woff') format('woff');
    }

FontAwesome Icons

These guidelines recommend using the free FontAwesome icon set, currently v5.13.0, and hosting it locally.

NOTE Hosting FontAwesome instead of loading it from some CDN ensures the icons are available to the application even if the CDN is down or not reachable; see here for more info.

  • Download and expand the FontAwesome archive from here
  • Create directory src/assets/fontawesome
  • Copy directories css and webfonts from the expanded archive to src/assets/fontawesome
  • Add the following link element to the head element of your index.html file to load the new icons:
    <link rel="stylesheet" href="assets/fontawesome/css/all.css">

Bootstrap integration

If needed, add a dependency on bootstrap to your application:
    npm install bootstrap

Make sure you add bootstrap.bundle.js to the scripts element in angular.json:
    "scripts": [
        "node_modules/bootstrap/dist/js/bootstrap.bundle.js",
        ...
    ]

NOTE It's not necessary to include an explicit dependency on popper.js

ng-bootstrap integration

If needed, add a dependency on @ng-bootstrap/ng-bootstrap and @angular/localize to your application. It will allow using Angular wrappers around Boostrap controls like Dropdown or Popover (localize is a derived dependency):
    npm install @ng-bootstrap/ng-bootstrap
    ng add @angular/localize

NOTE If you want to mix and match boostrap and ng-bootstrap components, like dropdown and ngbDropdownMenu, you need to explicitly integrate with both products.

Application structure

The application component (app.component.html) should include a permanent page header and the main child component component below it: if you are using Angular's routing, that will be your <router-outlet>. The whole thing should be wrapped in a .container-fluid to take up the whole window.

Main child

The main child's structure is coded in app.component.html and it should be a 1×1 Bootstrap grid taking up the whole space (with container-fluid).
In addition to providing a structure blueprint for the application, it will ensure proper right and left padding.
The one cell should include header and router outlet.

<div class="container-fluid">
  <div class="row">
    <div class="col-12">

      <page-header></page-header>
      <router-outlet></router-outlet>

    </div>
  </div>
</div>

NOTES
  • In a Bootstrap grid "content must be placed within columns and only columns may be immediate children of rows." Nothing will work if you don't follow that rule strictly.
  • It's not necessary to specify the width of the last column, it will take up all remaining space.

Application header bar

The permanent page header for an application using Angular routing is shown below.

  • <div class="container-fluid alma-blue">
      <div class="row">
        <div class="col d-flex align-items-center">

          <a class="tab-head home-tab" routerLink="/">
            <img class="alma-logo" src='assets/img/alma-logo.jpg' alt="ALMA logo">
            <span class="alma-app-name"> Example Tool </span>
            <span class="alma-sw-version"> {{ version }} </span>
          </a>

          <a routerLink="/stars" class="tab-head">
            <i class="fas fa-star has-text"></i> Stars </a>

          <a routerLink="/trees" class="tab-head">
            <i class="fas fa-tree has-text"></i> Trees </a>

          <span class="flex-fill"></span>

          <a routerLink="/profile" class="tab-head">
            <i class="fas fa-user has-text"></i> {{ user }} </a>

        </div>
      </div>
    </div>

Usage

These guidelines are based on Bootstrap, please refer to their documentation for general information. In this section we document any definitions overriding Bootstrap's own, and ALMA-specific usage.

The default text font is Lato, at 14px size. (Make that 1em instead?) See the Fonts section for more info.

ALMA branding

ALMA "branding" is very limited and consists of its logo and the ALMA blue color — the vertical linear blue gradient of the ALMA logo background.

You can use the ALMA blue as the background color of any element. For instance:

  • <label class="alma-blue" ...>ALMA blue</label>
NOTE

"ALMA blue" is defined as linear-gradient(to bottom, #004f87 0%, #103073 100%)
If using the alma-blue class or the gradient is not possible, or convenient, the ALMA primary blue color #004f87 should be used instead.

  • <label style="background-color: #004f87; ...>ALMA primary blue</label>

Primary and secondary actions

These guidelines distinguish between primary and secondary actions. Primary actions are the most important actions in any given context; secondary actions are optional, additional actions that may be useful.
Actions are further categorized as destructive if their effect cannot be recovered.

In general, there should be a single primary button in any given context; several secondary actions may be offered.

NOTE

Primary buttons are often called Call to Action (CTA) buttons in the literature; Call to Attention is also used. (See for instance here .)

Our choice of terms, primary and secondary, has historical reasons and reflects on the class names used for the implementation. Unfortunately, that conflicts with Bootstrap's naming conventions for colors and can cause confusion.

When buttons are placed together in a row or group, they should be ordered by action type:

  1. Primary action, or primary destructive action
  2. Secondary non-destructive actions
  3. Secondary destructive actions

  • <button class="btn btn-primary"> Primary action </button>
    <button class="btn btn-danger"> Primary destructive action </button>
    <button class="btn btn-secondary"> Secondary action </button>
    <button class="btn btn-danger btn-secondary"> Secondary destructive action </button>

Alerts

Bootstrap's own alert implementation can be configured using one of the known contextual classes: .alert-primary, .alert-secondary, .alert-success, .alert-danger, .alert-warning, .alert-info, .alert-light and .alert-dark.

  • <div class="alert alert-success alert-dismissible fade show" role="alert">
        <i class="fas fa-info-circle"></i>
        Your proposal ...
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
    </div>
Using `ngb-alert`

Before Bootstrap 5, you had to define alerts as ngb-alert elements: see here for more info.
For instance:

  • This is an "info" alert that can be dismissed
  • <ngb-alert class="alert" type="info" [dismissible]="true">
        <i class="fas fa-info-circle"></i>
        This is an "info" alert ...
    </ngb-alert>

Buttons

Define buttons using one or more of the following classes.
See sec. Primary and secondary actions for a description of the semantics of primary and secondary.

  • Active buttons
  • Disabled buttons
  • <button type="button" class="btn btn-danger btn-secondary"> btn-danger btn-secondary </button>
    ...

NOTE Form reset buttons are discouraged, see for instance item 48, "Avoid using ‘clear’ or ‘reset’ buttons", in this text.

Button rows and groups

When lining up buttons in a horizontal row, use the .button-row class to provide adequate spacing, as in the example above:
<div class="button-row">
  <button class="btn btn-primary btn-sm"> btn-primary </button>
  <button class="btn btn-secondary btn-sm"> btn-secondary </button>
  <button class="btn btn-danger btn-sm"> btn-danger </button>
  ...
</div>

NOTE Form buttons should be pushed to the right side of the form, use .form-buttons together with .button-row to achieve that.

Tightly coupled buttons should be collected in a .btn-group element, which provides rounded corners on the left and right sides. (.btn-group is provided by Bootstrap.)

  • <div class="btn-group">
      <button> ... </button>
      <button> ... </button>
      <button> ... </button>
    </div>

Buttons can also be stacked vertically with the .button-column class, which also provides adequate spacing.
For best results, stacked buttons should have the same width: in the example below, that's achieved via the .fixed-width-button class.

  • <div class="button-column">
      <button class="btn btn-success fixed-width-button"> Top </button>
      <button class="btn btn-success fixed-width-button"> Middle </button>
      <button class="btn btn-success fixed-width-button"> Bottom </button>
      ...
    /div>

Busy indicators

"Busy" indicators show that something is going on in the background and the user needs to wait. Interaction with the application is not possible, and in fact the application UI is partially hidden away to convey that message.

To introduce a busy indicator in your application you will need to:

  1. Add a dependency to the library:
    npm install @almaobservatory/busy-indicator
  2. Place a <busy-indicator> element at the top level of your application, for instance in the header bar
  3. Identify where you want the indicator to appear and disappear, and call the show() and hide() methods of the BusyIndicatorService as appropriate.

Clicking on the button below will make the indicator appear, then disappear again after a couple of seconds.

  • <button class="btn btn-secondary btn-sm" (click)="showBusyIndicator()">
        <i class="fas fa-redo"></i>
        Show busy indicator
    </button>

  • constructor(private busyIndicatorService: BusyIndicatorService) {
    }
    ...
    showBusyIndicator() {
        this.busyIndicatorService.show();
        setTimeout( () => this.busyIndicatorService.hide(), 2000 );
    }

NOTE

The BusyIndicatorService offers a subscription() method returning a Subject<boolean>. Applications can use it to be notified when the indicator appears (Subject value is true) or disappears (false).
this.busyIndicatorService.subscription().subscribe( showing => ... );

Checkboxes

For checkboxes, you can use the standard Bootstrap 5 classes.

  • <div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="checkbox" id="ea" [(ngModel)]="eaCheckBox" >
            <label class="form-check-label" for="ea"> EA </label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="checkbox" id="eu" [(ngModel)]="euCheckBox" >
            <label class="form-check-label" for="eu"> EU </label>
        </div>
        ...
    </div>

Radio buttons

As with checkboxes, with radio buttons you can use the standard Bootstrap 5 classes.

  • <div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" value="EA" id="ea" [(ngModel)]="execRadio" >
            <label class="form-check-label" for="ea"> EA </label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" value="EU" id="eu" [(ngModel)]="execRadio" >
            <label class="form-check-label" for="eu"> EU </label>
        </div>
        ...
    </div>

To implement dropdowns, or pulldown menus, you should use the ng-bootstrap Dropdown classes .

  • <button ngbDropdown class="btn btn-secondary btn-sm">
        <a ngbDropdownToggle>
            Edit
        </a>
        <div ngbDropdownMenu>
            <a (click)="..." ngbDropdownItem>
                <i class="fas fa-cut"></i> Cut (Ctrl-X)
            </a>
            <a (click)="..." ngbDropdownItem>
                <i class="fas fa-copy"></i> Copy (Ctrl-C)
            </a>
            ...
        </div>
    </button>

Forms

Notable rules for composing forms include:

  • Use reactive forms instead of template forms, to take advantage of the added flexibility

  • Input labels should be placed above the input fields, taking care of using the .form-group and .form-control classes

    <div>
        <label> Email </label>
        <input type="text" formControlName="email" class="form-control">
    </div>

  • Validation errors should be reported by displaying a red border around the input field and showing an error description in red below the input field. Note the use of classes .invalid-feedback and (dynamic) .is-invalid.

    <div>
        <label> Email </label>
        <input type="text" ... [ngClass]={ 'is-invalid': email.errors }">
        <div *ngIf="email.errors" class="invalid-feedback">
            <div *ngIf="email.errors.required">Email is required</div>
            <div *ngIf="email.errors.email">Email must be a valid email address</div>
        </div>
    </div>

  • Any validation errors should mark the entire form as invalid.

    <h5>
        New User Registration
        <i *ngIf="profileForm.invalid" class="fas fa-asterisk dirty"></i>
    </h5>

    TODO Class .dirty should be renamed to .invalid to avoid confusion with Angular's usage of dirty.

  • Action buttons should be grouped in a .form-buttons .button-row div

    <div class="form-buttons button-row">
        button class="btn btn-primary mr-1" (click)="submit()"> Register </button>
        button class="btn btn-secondary" (click)="reset()"> Reset </button>
    </div>

The example below shows an example reactive form. Try causing validation errors by clearing the input fields (they are both required), typing an invalid email address (e.g. user@), or shortening the password to less than six characters.

NOTE The rounded border does not belong to the UI Guidelines, it was introduced for readability.

New User Registration

For a full example please see the source files of this section, OBOPS/npm/alma-ui-guidelines-docs/src/app/forms-section/forms-section.component.html and forms-section.component.ts

Icons

Whenever possible and meaningful, action buttons and menu items should include an icon in addition to, or instead of, some short explanatory text. All public FontAwesome icons are available.

   

<button type="button" class="btn btn-primary btn-sm">
  <i class="fas fa-upload"></i>
  &nbsp; Upload
</button>

NOTE
You may want to add some space between the icon and the following text. In the example above, that's achieved with a hard space (&nbsp;), but you can also add class has-text to the <i> element.

Text can be omitted when icons are unambiguous, as in this example:

Modals

Modal templates include a close button in the top-right corner; it's labeled with a "cross" icon: Close button
Unless the modal's semantics require it, no extra close button should be added.

To change a modal's size you can se one of the options of the open() method, like
this.modalService.open(content, { size: 'lg' });
See Modal with options for more info.

  • <ng-template #templateBasedModal let-modal>
        <div class="modal-header">
            <div class="modal-title"> Template-based modal </div>
            <button class="btn btn-close" type="button" (click)="modal.dismiss()"></button>
        </div>
        <div class="modal-body">
            ...
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-primary" (click)="modal.close( 'Save' )"> Save </button>
            <button type="button" class="btn btn-danger" (click)="modal.dismiss()"> Cancel </button>
        </div>
    </ng-template>
NOTE

Buttons in the bottom .modal-footer element do not need to be wrapped in a further .button-row element.

Selects (Dropdowns)

Selects (or dropdowns) allow choosing an item from a list of options.

  • Component class

    selectedOption: string | undefined;
    options = [ 'α alpha', 'β beta', 'γ gamma', 'δ delta' ];
    ...
    setSelectedOption( option: string ) {
        this.selectedOption = option;
    }

  • HTML

    <div ngbDropdown>
        <button class="btn" ngbDropdownToggle [ngClass]="{ 'text-muted': ! selectedOption }">
            {{ selectedOption ? selectedOption : 'Select one' }}
        </button>
        <div ngbDropdownMenu>
            <button ngbDropdownItem *ngFor="let option of options" (click)="setSelectedOption(option)">
                {{ option }}
            </button>
        </div>
    </div>

NOTES

  • The dropdown's size defaults to 14em. To change that, add the following definitions to your component's CSS file with the desired width; notice that the dropdown items' width should be about 0.15em smaller.

    .dropdown-item {
         min-width: 19.85em;
    }
    .dropdown-toggle {
        min-width: 20em;
    }

  • The Bootstrap dropdown doesn't deal very well with an empty set of options. It is suggested to disable the toggle if the there are no options to choose from:

    <button class="btn" ngbDropdownToggle [disabled]="options.length === 0" ... > ... </button>

Tables

Tables should use the .table-bordered Boostrap class
Use modifier classes .thead-light or .thead-dark to make theads appear light gray or ALMA blue.
Experiment with modifier classes .table-sm and .table-compact to find a compromise between readability and use of available space.

  • ALMA IDNameE-mailARCInstitutionManual CalManual ImgWeblog ReviewQA2 Approval
       fmercuryFreddy Mercuryfmercury@eso.orgEUESO
       bmayBrian Maybmay@alma.clJAOALMA
       jhexJimi Hendrixjhex@nrao.eduNANRAO

  • <table class="table table-sm table-compact table-bordered">
        <thead class="thead-light">
        <tr>
            <th>ALMA ID</th>
            <th>Name</th>
            ...
        </tr>
        </thead>
        <tbody>
        <tr *ngFor="...">
            <td> ... </td>
            ...
        </tr>
        </tbody>
    </table>

The UI guidelines largely follow Bootstrap's settings for text styling.

  • Bold
    ipsum dolor sit amet, consectetur adipiscing elit
    Italicized
    sed do eiusmod tempor incididunt ut labore
    Highlighted
    dolore magna aliqua. Ut enim ad minim veniam, quis
    Small
    exercitation ullamco laboris nisi ut aliquip
    Links
    This is a link to ESO, followed by followed by.
    Code
    if (a === 0) { ... }

  • <dl>
        <dt>Bold</dt> <dd> <strong> ipsum dolor sit amet... </strong> </dd>
        <dt>Italicized</dt> <dd> <em> sed do eiusmod tempor... </em> </dd>
        ...
    </dl>

The color chosen by Bootstrap for anchor links provides sufficient contrast to the surrounding black text, but is too bright in itself. When combining links in a navigation bar or table of contents, Bootstrap switches to a gray color, defined as rgb(108, 117, 125) or #6C757D.
See for instance this page.

On hover, table of contents links are shown underlined (see for instance the table of contents of this document); navigation bar links appear as buttons.
That behavior is captured by classes .toc-link and .navigation-link, respectively.

  • Table of contents link: Hover over me
    Navigation bar link: Hover over me
  • Table of contents link: <a class="toc-link"> Hover over me </a>
    Navigation bar link: <a class="navigation-link"> Hover over me </a>

Tooltips (Popups)

To implement tooltip text showing on hover, the UI Guidelines provide a CSS-only partial re-implementation of the Semantic UI "popups".
For more complex use cases (e.g., dynamically generated tooltip text; using a template for the tooltip body), see below.

  • <button type="button" tooltip-text="Are you really sure?" class="btn btn-danger">
        Delete all
    </button>

Attributes tooltip-position and tooltip-size can be used to customize the popup.

NOTES

  • The default value for tooltip-position is top center. Valid values are:
    • top left, top center, top right
    • bottom left, bottom center, bottom right
    • left
    • right
  • The default value for tooltip-size is md. Valid values are: sm, md, lg, xl, xxl
  • Tooltip text can be broken down in multiple lines. When rendering, line breaks
    are preserved but whitespace is compressed.

    • <button class="btn btn-primary"
              tooltip-text="First line.
                            Second line.
                            Third and last line.">
          Hover over
      </button>
Dynamic tooltips

Dynamic tooltips show text that’s computed at runtime and displayed via Angular interpolation; they are based on ngbPopover. (See
the ngbPopover specs for more info.)

  • Hover over me please!
  • <span ngbPopover="{{ thankYou }}"
          triggers="mouseenter:mouseleave"
          popoverClass="alma-ngb-popover"
          container="body" >
        Hover over me please!
    </span>

NOTES

  • Attribute container="body" (see here) is mandatory when the ngbPopover is used on a <tr> or <td> element.
  • Make sure to include the popoverClass="alma-ngb-popover" directive to align the styling of ngbPopover-based and CSS-only tooltips!
Template-based tooltips

Template-based tooltips are also based on ngbPopover. (See the template-specific specs for more info.)

  • Hover over me too!
  • <ng-template #popupTemplate>
        <h5> A template-based tooltip </h5>
        This <em>tooltip text</em> is defined ...
    </ng-template>

    <span [ngbPopover]="popupTemplate"
          triggers="mouseenter:mouseleave"
          popoverClass="alma-ngb-popover"
          container="body" >
        Hover over me too!
    </span>
ngbTooltip

As of Bootstrap 5, <ngb-tooltip> elements are deprecated.

Bootstrap tooltips can be implemented as ngbTooltip-based components as well.
They have a very similar structure to SUI popups but are rather inflexible. A solution based on ngbPopover allows better styling and finer control, and it should always be preferred over ngbTooltip.
Anyway, below is an example usage.


  • <button class="btn btn-primary"
          ngbTooltip="{{ thankYou }} I'm an ngbTooltip"
          triggers="mouseenter:mouseleave"
          tooltipClass="alma-ngb-tooltip"
          placement="right">
        Hover over...
    </span>

NOTES

  • An ngbTooltip-based tooltip placed on a <td> element can interfere with table layout.
    If that happens, you can try adding the container="body" directive.
    Another possible workaround is to wrap the <td> element in a <span> or <div> element.
    <div>
        <td [ngbTooltip]="popupTemplate"> ... </td>
    </div>
  • The width of an ngbTooltip-based tooltip can be changed by redefining class .alma-ngb-tooltip in your styles.css file. (It will not work at the component level.)

    .alma-ngb-tooltip { width: 5rem; }

Change log

3.4.108-Sep-2023 Improved Bootstrap / ng-bootstrap integration section
3.2.207-Apr-2022 Improved form example
3.2.129-Mar-2022 Ported to v2.0.1 of the UI Guidelines
3.0.20.1 Ported to Angular 13
3.0.20 Ported to v 1.1.20 of the UI Guidelines
3.0.14 to 3.0.19 Skipped, to align this doc's version with the UI Guidelines version
3.0.13 Ported to v 1.1.19 of the UI Guidelines
3.0.9 to 3.0.12 Minor changes
3.0.8 Added Primary and secondary actions, corrected example form, fixed naming and styling of classes
3.0.7Added this Change log
3.0.6Added Text and links