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.
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.
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/...
.
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.
@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');
}
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.
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">
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 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. 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.
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>
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>
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" 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>
"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>
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.
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:
<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>
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>
Before Bootstrap 5, you had to define alerts as ngb-alert elements: see here for more info.
For instance:
<ngb-alert class="alert" type="info" [dismissible]="true">
<i class="fas fa-info-circle"></i>
This is an "info" alert ...
</ngb-alert>
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.
<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.
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 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:
npm install @almaobservatory/busy-indicator
<busy-indicator>
element at the top level of your application, for instance in the header bar 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 );
}
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 => ... );
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>
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>
Notable rules for composing forms include:
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.
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>
Upload
</button>
), but you can also add class has-text
to the <i>
element. Text can be omitted when icons are unambiguous, as in this example:
Modal templates include a close button in the top-right corner; it's labeled with a "cross" icon:
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, likethis.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>
Buttons in the bottom .modal-footer
element do not need to be wrapped in a further .button-row
element.
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 should use the .table-bordered
Boostrap class
Use modifier classes .thead-light
or .thead-dark
to make thead
s 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 ID | Name | ARC | Institution | Manual Cal | Manual Img | Weblog Review | QA2 Approval | ||
---|---|---|---|---|---|---|---|---|---|
fmercury | Freddy Mercury | fmercury@eso.org | EU | ESO | |||||
bmay | Brian May | bmay@alma.cl | JAO | ALMA | |||||
jhex | Jimi Hendrix | jhex@nrao.edu | NA | NRAO |
<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.
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: <a class="toc-link"> Hover over me </a>
Navigation bar link: <a class="navigation-link"> Hover over me </a>
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
tooltip-position
is top center
. Valid values are: top left
, top center
, top right
bottom left
, bottom center
, bottom right
left
right
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 show text that’s computed at runtime and displayed via Angular interpolation; they are based on ngbPopover. (See
the ngbPopover specs for more info.)
<span ngbPopover="{{ thankYou }}"
triggers="mouseenter:mouseleave"
popoverClass="alma-ngb-popover"
container="body" >
Hover over me please!
</span>
NOTES
container="body"
(see here) is mandatory when the ngbPopover is used on a <tr>
or <td>
element. popoverClass="alma-ngb-popover"
directive to align the styling of ngbPopover
-based and CSS-only tooltips! Template-based tooltips are also based on ngbPopover. (See the template-specific specs for more info.)
<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>
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
<td>
element can interfere with table layout.container="body"
directive.<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; }
3.4.1 | 08-Sep-2023 | Improved Bootstrap / ng-bootstrap integration section |
3.2.2 | 07-Apr-2022 | Improved form example |
3.2.1 | 29-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.7 | Added this Change log | |
3.0.6 | Added Text and links |