m365-msteams-northwind-app-samples

Sample code for Microsoft Teams application with SSO, Bots, Messaging extensions and other capabilities.

Teams App Camp

Lab A03: Teams styling and themes

Overview

This lab is part of Path A, which begins with a Northwind Orders application that already uses Azure AD.

In this lab you will begin with the application in folder A02-after-teams-sso, make changes as per the steps below to achieve what is in the folder A03-after-apply-styling.

See project structures comparison in Exercise 1.

Features


The project structure when you start of this lab and end of this lab is as follows. Use this depiction for comparison. On your left is the contents of folder A03-TeamsSSO and on your right is the contents of folder A04-StyleAndThemes.

Project Structure Before Project Structure After
A02-after-teams-sso
    β”œβ”€β”€ client
    β”‚   β”œβ”€β”€ components
    β”‚       β”œβ”€β”€ navigation.js
    β”‚   └── identity
    β”‚       β”œβ”€β”€ identityClient.js
    β”‚       └── userPanel.js
    β”œβ”€β”€ modules
    β”‚   └── env.js
    β”‚   └── northwindDataService.js
    β”‚   └── πŸ”ΊteamsHelpers.js
    β”œβ”€β”€ pages
    β”‚   └── categories.html
    β”‚   └── categories.js
    β”‚   └── categoryDetails.html
    β”‚   └── categoryDetails.js
    β”‚   └── myOrders.html
    β”‚   └── orderDetail.html
    β”‚   └── orderDetail.js
    β”‚   └── privacy.html
    β”‚   └── productDetail.html
    β”‚   └── productDetail.js
    β”‚   └── termsofuse.html
    β”œβ”€β”€ index.html
    β”œβ”€β”€ index.js
    β”œβ”€β”€ πŸ”Ίnorthwind.css
    β”œβ”€β”€ manifest
    β”‚   └── makePackage.js
    β”‚   └── πŸ”Ίmanifest.template.json
    β”‚   └── northwind32.png
    β”‚   └── northwind192.png
    β”‚   └── constants.js
    β”‚   └── identityService.js
    β”‚   └── northwindDataService.js
    β”‚   └── server.js
    β”œβ”€β”€ .env_Sample
    β”œβ”€β”€ .gitignore
    β”œβ”€β”€ package.json
    β”œβ”€β”€ README.md
A03-after-apply-styling
    β”œβ”€β”€ client
    β”‚   β”œβ”€β”€ components
    β”‚       β”œβ”€β”€ navigation.js
    β”‚   └── identity
    β”‚       β”œβ”€β”€ identityClient.js
    β”‚       └── userPanel.js
    β”œβ”€β”€ modules
    β”‚   └── env.js
    β”‚   └── northwindDataService.js
    β”‚   └── πŸ”ΊteamsHelpers.js
    β”œβ”€β”€ pages
    β”‚   └── categories.html
    β”‚   └── categories.js
    β”‚   └── categoryDetails.html
    β”‚   └── categoryDetails.js
    β”‚   └── myOrders.html
    β”‚   └── orderDetail.html
    β”‚   └── orderDetail.js
    β”‚   └── privacy.html
    β”‚   └── productDetail.html
    β”‚   └── productDetail.js
    β”‚   └── termsofuse.html
    β”œβ”€β”€ index.html
    β”œβ”€β”€ index.js
    β”œβ”€β”€ πŸ”Ίnorthwind.css
    β”œβ”€β”€ πŸ†•teamstyle.css
    β”œβ”€β”€ manifest
    β”‚   └── makePackage.js
    β”‚   └── πŸ”Ίmanifest.template.json
    β”‚   └── northwind32.png
    β”‚   └── northwind192.png
    β”‚   └── constants.js
    β”‚   └── identityService.js
    β”‚   └── northwindDataService.js
    β”‚   └── server.js
    β”œβ”€β”€ .env_Sample
    β”œβ”€β”€ .gitignore
    β”œβ”€β”€ package.json
    β”œβ”€β”€ README.md

In the project structure, on the right under A03-after-apply-styling, you will see emoji πŸ†• near the files & folders. They are the new files and folders that you need to add into the project structure.

Exercise 1: Add CSS

Step 1: Create a CSS file for Teams theme styles

Create a file teamstyle.css in the client folder and copy below code block into it. These styles are based on the Teams UI Toolkit Figma. If you’re working in React, you may want to use the Teams UI Toolkit React Components.

:root {
  /* common */
  --brand-color: #6264A7;
  --button-color: #6264A7;
  --button-text-color: #fff;
  --button-hover-color: rgb(88, 90, 150);
  --button-hover-text-color: #fff;
  --button-active-color: rgb(70, 71, 117);
  --button-active-text-color: #fff;
  --button-border: 1px solid hsla(0,0%,100%,.04);
  --button-shadow: rgb(0 0 0 / 25%) 0px 0.2rem 0.4rem -0.075rem;
  --button2-color: #fff;
  --button2-text-color: rgb(37, 36, 35);
  --button2-hover-color: rgb(237, 235, 233);
  --button2-active-color: rgb(225, 223, 221);
  --button2-border: 1px solid rgb(225, 223, 221);
  --button2-shadow: rgb(0 0 0 / 10%) 0px 0.2rem 0.4rem -0.075rem;
  --button-disabled-color: rgb(237, 235, 233);
  --button-disabled-text-color: rgb(200, 198, 196);
  --input-background-color: rgb(243, 242, 241);
  --input-border-color: transparent;
  --input-border-width: 0 0 0.1429rem 0;
  --input-focus-border-color: transparent;
  --input-focus-border-bottom-color: #6264A7;
  --table-color: transparent;
  --table-border: 1px solid rgb(237, 235, 233);
  --border-color: rgb(237, 235, 233);
  
  /* light theme */
  --font-color: rgb(37, 36, 35);
  --background-color: #fff;
  --link-color: #6264A7;
  --border-color: #E1DFDD;
  --warning-color: #C4314B;
}

[data-theme="dark"] {
  --font-color: #fff;
  --background-color: transparent;
  --link-color: #A6A7DC;
  --border-color: #605E5C;
  --warning-color: #F9526B;
}

[data-theme="contrast"] {
  --brand-color: #ffff01;
  --font-color: #fff;
  --link-color: #ffff01;
  --background-color: transparent;
  --border-color: #fff;
  --button-color: transparent;
  --button-text-color: #fff;
  --button-hover-color: #ffff01;
  --button-hover-text-color: #000;
  --button-active-color: #1aebff;
  --button-active-text-color: #000;
  --button-border: .2rem solid #fff;
  --input-background-color: transparent;
  --input-border-color: #fff;
  --input-border-width: 1px;
  --input-focus-border-color: #1aebff;
  --input-focus-border-bottom-color: #1aebff;
  --warning-color: #ffff01;
}

body {
  background-color: var(--background-color);
  color: var(--font-color);
  box-sizing: border-box;
  font-size: 14px;
}

a, a:visited {
  color: var(--link-color);
  text-decoration: none;
}

a:hover, a:active {
  text-decoration: underline;
}

table, caption, tbody, tfoot, thead, tr, th, td { /*reset */
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: middle;
  border-collapse: collapse;
}
table {
  display: table;
  background-color: var(--table-color);
  border-spacing: 0;
}
tr {
  display: table-row;
  border-bottom: var(--table-border);
}
th, td {
  display: table-cell;
  height: 3.4286rem;
  padding: 0 0.5714rem;
}

th{
  font-weight: 600;
}

button, input, optgroup, select, textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}

button {
  min-width: 6rem;
  font-weight: 600;
  height: 2rem;
  padding: 0 1.25rem;
  vertical-align: middle;
  border-radius: 2px;
  background-color: var(--button-color);
  color: var(--button-text-color);
  border: var(--button-border);
  box-shadow: var(--button-shadow);
  overflow: visible;
}
button:hover {
  background-color: var(--button-hover-color);
  color: var(--button-active-text-color);
}
button:active {
  background-color: var(--button-active-color);
  color: var(--button-active-text-color);
  box-shadow: none;
}
button[disabled] {
  background-color: var(--button-disabled-color);
  color: var(--button-disabled-text-color);
  box-shadow: none;
}
button:not(:last-child) {
    margin-right: 0.5rem;
}

button.secondary {
  background: var(--button2-color);
  border: var(--button2-border);
  color: var(--button2-text-color);
  box-shadow: var(--button2-shadow);
}
button.secondary:hover {
  background-color: var(--button2-hover-color);
}
button.secondary:active {
  background-color: var(--button2-active-color);
}

label {
  margin: 0 0.7143rem 0.2857rem 0;
}

input {
  background-color: var(--input-background-color);
  padding: 0.3571rem 0.8571rem;
  line-height: unset;
  border-width: var(--input-border-width);
  border-radius: 0.2143rem 0.2143rem 0.1429rem 0.1429rem;
  border-color: var(--input-border-color);
  outline-style: none;
  overflow: visible;
  margin-bottom: 1.4286rem;
}
input:focus {
  border-color: var(--input-focus-border-color);
  border-bottom-color: var(--input-focus-border-bottom-color);
}

[type=checkbox], [type=radio] {
  padding: 0;
  margin-right: 0.5rem;
}

hr {
  border: 0;
  height: 1px;
  background: var(--border-color);
}

/* Text styling classes */

.medium {
  font-size: 1rem;
}
.small {
  font-size: 0.8571rem;
}
.smaller {
  font-size: 0.7143rem;
}
.large {
  font-size: 1.2857rem;
}
.larger {
  font-size: 1.7143rem;
}
.danger, .warning, .alert, .error {
  color: var(--warning-color);
}


/* Font */

@font-face {
  font-family: 'Segoe UI Web';
  src: url('https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-regular.woff2') format('woff2'), url('https://static2.sharepointonline.com/files/fabric/assets/fonts/segoeui-westeuropean/segoeui-regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
}

body {
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  font-family: 'Segoe UI', 'Segoe UI Web', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
}

This CSS contains basic stylings for Teams UI. After applying the styles, the existing web app gets more consistent look-and-feel to Teams client.

The CSS also includes dark and high-contrast mode. The color switch is done with CSS variables. In the next exercise, you will enable the theme switching functionality in JavaScript.

Step 2: Import the new CSS

To import the teamstyle.css so it is loaded in all pages, add this statement at the top of your northwind.css file.

@import "teamstyle.css";

Exercise 2: Update and run the project

Step 1: Modify modules\teamsHelpers.js

Import the Teams JavaScript SDK, which creates a global object microsoftTeams that we can use to access the SDK. You could also load it using a <script> tag or, if you bundle your client-side JavaScript, using the @microsoft/teams-js npm package. Copy below import statement to the top of the file:

import 'https://statics.teams.cdn.office.net/sdk/v1.11.0/js/MicrosoftTeams.min.js';

The Teams client supports three themesβ€”light mode, dark mode, and high contrast mode (which is an acceissibility feature for users with low visual acuity). As the users switch the themes, your application should also switch its theme so as to blend in. To detect theme switching in Teams client we’ll have to use the global microsoftTeams’s context.

We β€˜ll add a function setTheme() to switch the css between the application’s native style and the team’s themes. Add this code to teamsHelpers.js:

function setTheme (theme) {
    const el = document.documentElement;
    el.setAttribute('data-theme', theme); // switching CSS
};

Every time the theme is detected/changed, the script applies a data-theme value in the root of the content, like, <html data-theme='dark'>, so the teamstyle.css will use a correct set of colors & styles for each theme. The color change is done with the CSS variables.

Now add in-line code into teamsHelpers.js to detect current context with getContext() and set the theme to match the current theme in Microsoft Teams. The code also registers an event handler that updates the application’s theme when a user changes the theme in Microsoft Teams.

Copy and paste below code block for this purpose:

microsoftTeams.initialize(() => {
    // Set the CSS to reflect the current Teams theme
    microsoftTeams.getContext((context) => {
        setTheme(context.theme);
    });
    // When the theme changes, update the CSS again
    microsoftTeams.registerOnThemeChangeHandler((theme) => {
        setTheme(theme);
    });
});

Step 2: Start your local project

Now it’s time to run your updated application and run it in Microsoft Teams. Start the application by running below command:

npm start

Step 3: Run the application in Teams client

Once the teams tab app is added, the personal tab will open My Orders tab. The application will now have the team’s native look and feel.

Teams tab style

Here’s how to change themes in teams client. Notice how the teams tab app also detects and changes its theme.

Change theme

Next steps

Congratulations! You have completed all core application development labs in path A. You may continue with any of the following labs.