@abt-desk/realogy-mat-theme

This theme captures colors applied to Angular Material Components for use when building Realogy products.

Usage no npm install needed!

<script type="module">
  import abtDeskRealogyMatTheme from 'https://cdn.skypack.dev/@abt-desk/realogy-mat-theme';
</script>

README

Realogy Angular Material Theme

This theme captures colors applied to Angular Material Components for use when building Realogy products.

Additionally, typography and component style overrides are provided to help align with our style guide and provide consistency in implementation.

See Angular Material component documentation

See example styles applied on StackBlitz ⚡️

See Google's guidelines on best practices

Installation

  • Install Angular Material and it's peer dependencies as described on material.angular.io

  • Skip including a prebuild theme

  • Install theme via npm. npm install @abt-desk/realogy-mat-theme

  • Add following code to your global style.scss

      @import '~@abt-desk/realogy-mat-theme/src/theme';
      @include angular-material-theme();
    
  • Copy the contents of components.scss to your global style.scss file

Rebranding

If you'd like to apply a new brand to make use of a material theme, please note the following:

  1. Establish your primary, accent and warn (optional) colors. If your brand palette lacks an accent color, you can use the same values in primary for your accent. Make sure you have your color values in hex format.

  2. Test the contrast of your chosen colors for accessibility. Put the values in the foreground color field and click outside the field to return a contrast ratio.

That value must be 4.5:1 or greater to ensure the palette will be applied in a way that's compliant with accessibility standards. If it's under that ratio, work with creative teams to acquire a darker value for colors that don't pass the check.

  1. Visit the Material Palette Generator

  2. Click on the color field in the top right of the box on the left of the page. Replace the hex value in the text field with your own for your primary color and click choose.

  3. Click the copy icon to the left of the color field, and select the appropriate output format (in our case, it's Angular JS2). Copy the palette to use in your theme's scss file.

  4. Repeat the process for your accent and warn colors.

  5. Fork the theme and replace the palettes in realogy-theme.scss with your own.

Guidance on Specific Component Usage

Forms

When using forms, ensure fields are using the outlined variation shown below for ideal readability & accessibility:

    <mat-form-field appearance="outline">

When fields have character limits, ensure the hint includes the character limit, aligned at the end below the field so that users understand how close they are to reaching the limit. Example below:

    <mat-form-field appearance="outline">

      <mat-label>Last Name</mat-label>
      
      <input matInput #nickname placeholder="Enter last name" maxlength="50" />
      
      <mat-hint align="end">
      
        {{nickname.value.length}} / 50
        
      </mat-hint>
      
    </mat-form-field >

If applying a character limit hint to a textarea field, include ng-trim="false" in the markup to ensure the hint displays without errors. Example below:

<mat-form-field appearance="outline">

  <mat-label>Comments</mat-label>

  <textarea matInput name="CommentsBox" placeholder="Leave comments" maxlength="256" #Comments ng-trim="false"></textarea>

  <mat-hint align="end">{{Comments?.value?.length}} / 256</mat-hint>

</mat-form-field>

Buttons

When using buttons, we're making use 3 standard styles for any buttons including text. In the example markup below there's a primary, secondary and tertiary style.

    <button class="app-button" mat-raised-button color="primary">Filled</button>
    <button class="app-button" mat-stroked-button mat-tooltip="This is a tooltip!" color="primary">Outlined</button>
    <button class="app-button" color="primary" mat-button>Text</button>

Filled buttons use .mat-raised-button with a color of primary. They should be limited to the primary action on the page.

Outlined buttons use .mat-stroked-button with a color of primary. They should include any secondary actions on a screen where a larger visible touch target is appropriate.

Text buttons use .mat-button with a color of primary. They should include cancel & close actions on dialogs and similar use cases.

Select

When using the material select menu, do so in cases where the options include 4 or more items.

If fewer items exist, a button toggle may be more appropriate if there's room to display items upfront.

In cases where multiple select is needed on the component, include the multiple property on the component as referenced below:

    <mat-select [formControl]="states" multiple>

Dialog

When using the dialog, try to limit included actions to a primary action and the ability to cancel/close the dialog.

Display the primary action on the bottom right of the dialog, with the cancel action to its left. See example below:

<div matDialogActions>
  <button mat-button color="primary" [matDialogClose]="dialogInput.value">Cancel</button>
  <button mat-raised-button color="primary"[matDialogClose]="dialogInput.value">Call to action</button>
</div>

Tables

When using tables with multiple columns where the data is intended to be compared across multiple rows - make the first column sticky. Give that first column a max-width of no more than 40vw to ensure data across multiple columns can be compared in smaller viewports and a user doesn't miss out on data in other columns.

An example table with sticky columns might look like the following:

<div class="example-container mat-elevation-z8">
 <table mat-table [dataSource]="dataSource">
<!-- Name Column -->
<ng-container matColumnDef="name" sticky>
  <th mat-header-cell *matHeaderCellDef> Name </th>
  <td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<!-- Position Column -->
<ng-container matColumnDef="position">
  <th mat-header-cell *matHeaderCellDef> No. </th>
  <td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="weight">
  <th mat-header-cell *matHeaderCellDef> Weight </th>
  <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
  <th mat-header-cell *matHeaderCellDef> Symbol </th>
  <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
 </table>
</div>

Ideally, if the contents of a particular column contain alpha or numeric strings, you would enable user sorting on the columns. To do so, the table would need to include the matSort property, and column headers in the label row would need mat-sort-header. Because sort headers are treated like buttons, be sure to keep the column header label text succinct, as it will not wrap to a second line. When those labels exceed available space, they extend as much as they can horizontally before cutting off.

Floating Action Buttons

When using the floating action button, ensure it's for either a primary additive action on a page, or a common set of actions that would provide persistent access within your application. Use a color of primary to ensure it stands out from content. If you would like the floating action button to contain children, as you might see on Google Calendar - refer to custom speed dial implementations. The Angular Material library's component lacks that functionality out of the box. For additional guidance on speed dial behavior, refer to Google documentation.

Here's an example of a simple floating action button:

  <button mat-fab class="app-fab"color="primary">
    <mat-icon aria-label="Help">help</mat-icon>
  </button>

Extended Floating Action Buttons

When using extended floating action buttons, we're positioning them towards the top right of the page, but not fixing their position - so users can scroll them out of view. The intent here is to have an action that stands out on the page, but wouldn't create a scenario where multiple persistent ui elements on the page obstruct content. While Google recommends placing these towards the top left or bottom right of the page, they conflict with positioning of standard floating actions buttons in the bottom right, and often with heavy headers and top level navigation towards the top left.

Datepickers

Ensure datepickers include an outlined appearance like other form fields. Example below:

  <mat-form-field appearance="outline">
    <input matInput [matDatepicker]="picker" placeholder="Choose a date">
    <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker></mat-datepicker>
  </mat-form-field>

Sliders

Always show a thumb label on sliders when changing numerical values. If that values represented include large numbers, use shorthand (500k) or have the label describe that the value is in thousands/millions/etc.. Below is an example:

<mat-card>
  <mat-card-content>
    <h2 class="example-h2">Slider</h2>
    <mat-slider class="example-margin" color="primary" thumbLabel>
    </mat-slider>
  </mat-card-content>
  <mat-card-content>
    <h2 class="example-h2">Slide Toggle</h2>
    <mat-slide-toggle color="primary" [disabled]="slider.disabled" [checked]="slider.checked" (change)="slider.checked = !slider.checked">Slide me!</mat-slide-toggle>
  </mat-card-content>
</mat-card>

Chips

When using chips, we have a few style overrides to be aware of (see app.component.scss). For consistency in use, we're keeping all text in chips lowercase, as well as applying some style changes to ensure that chips are distinguished from standard buttons. In addition to their traditional use as signifiers of applied filters, you can also use chips as representations of tags and categories applicable to a piece of content. In those cases, be sure to set selectable to false. For example:

<mat-card>
  <mat-card-content>
    <h2 class="example-h2">Material Chips</h2>
    <mat-chip-list [selectable]="false">
      <mat-chip [selectable]="false">
        Chip
      </mat-chip>
      <mat-chip [selectable]="false">
        Another Chip
      </mat-chip>
    </mat-chip-list>
  </mat-card-content>
</mat-card>

Checkboxes

When using checkboxes, set the color to primary, so they appear consistent cross-product. Examples:

  <mat-checkbox color="primary">Unchecked</mat-checkbox>

Radio Buttons

As with checkboxes, set radio button colors to primary for consistency. Additionally, give items within radio groups values, so that in cases where a page may have multiple groups, they behave as expected. Reference below:

  <mat-radio-group>
    <mat-radio-button color="primary" name="symbol" value="1">Option 1</mat-radio-button>
    <br><br>
    <mat-radio-button color="primary" name="symbol" value="2">Option 2</mat-radio-button>
    <br><br>
    <mat-radio-button color="primary" name="symbol" value="3">Option 3</mat-radio-button>
  </mat-radio-group>

Icon Buttons

When using icon buttons, it's advised to use them with tooltips and ensure aria-labels are provided for accessibility. Within our product suite, we're typically using the regular variant of the fontawesome pro icon font. For a list of glyphs at your disposal, you can use this cheatsheet as reference.

Button Toggles

Use button toggles in places of a radio group where a touch friendly selection of a few values would be more appropriate. Use in cases where there's a default selection applied and try to avoid an empty state. When there are fewer than 4 items for a user to choose from, use a button toggle instead of a select menu. Text in button toggles should be kept succinct. Our component style overrides sets the focused item in a group to our theme's primary color:

  .mat-button-toggle-button:focus {
    background-color:mat-color($primary, 500);
    color:white;
  }

Progress Indicators

Depending on the context, both Progress Spinners and Progress Bars can be used. If it wouldn't affect performance or require more work than its worth, determinate progress is preferred to indeterminate. In cases where content would typically load within 1 second or less, indeterminate is perfectly acceptable.

Spinners are typically used when loading additional content below a user's position on the page, for example, if listings are revealed in sets of 10. In cases where content has no skeleton loading placeholder, spinners make a good alternative.

Progress bars are typically used when the indicator is in a fixed position adjacent to content, and is more suited to task oriented applications than those involving content consumption.

In both cases, we're applying a primary color on implementation to ensure they're noticed. See below:

<mat-card>
  <mat-card-content>
    <mat-progress-spinner class="example-margin" color="primary" mode="indeterminate">
    </mat-progress-spinner>
  </mat-card-content>
</mat-card>

<mat-card>
  <mat-card-content>
    <label>
      <mat-progress-bar class="app-progress" mode="indeterminate" aria-label="Indeterminate progress-bar example"></mat-progress-bar>
    </label>
    <label>
      <mat-progress-bar class="app-progress" color="primary" mode="determinate" [value]="progress" aria-label="Determinate progress-bar example"></mat-progress-bar>
    </label>
  </mat-card-content>
</mat-card>

Typography

Our typography scss file includes commonly used typographic classes in Angular Material. .mat-h1 - .mat-5, .mat-body, .mat-display-2 - .mat-display-4, etc.. We are using REM units for typography to ensure readability for users of drastically varying browser zoom levels. Our relative units assume a default font size of 16pt if a user has their browser/settings at a 100% zoom. To change font sizing on specific elements in your application, provide them in your css as needed. It's recommended to use established font sizes on common elements such as paragraphs, buttons, tabs and others to ensure all users can quickly scan for functionality and easily read content.

Elevation

In order to give objects depth, material design can make use of an elevation property on an object that would serve to give that object a shadow in lieu of providing box-shadow styling. To use elevation, ensure your theme is imported and specify using the following helper: ''' .some-component{ @include mat-elevation(2); } ''' Using different values will change the perceived depth of the object, where 8 might provide a much heavier shadow than a 2 would. Learn about elevation helpers.