Tag

SharePoint Framework Design Series

Browsing

Introduction

Microsoft has an awesome web site called SharePoint Design. It provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

However, it does not tell you how to create those beautiful web parts.

This blog series is intended as a companion to the SharePoint Design site. It provides code samples and detailed how-to information for every design topic.

It should help you create web parts that look exactly like the ones on the SharePoint Design site.

So far, we have discussed the following topics:

In today's post, we'll continue our discussion about the web part layout patterns and discuss the compact layout.

What is the Compact layout?

Compact layout

According to the SharePoint Design site:

The compact layout is designed to show content in a smaller format and works the best in a one-third column. This layout can support a small image or icon and a few rows of text for a title, description, and/or metadata.

However, it seems that the compact layout is not only used in 1/3 columns. The SharePoint look book and the SharePoint Online Provisioning Service both make use of the compact layout with the Quick Links web part.

When to use the compact layout

Use the compact layout when the items in your web part have very little information. A title and an icon or a small thumbnail is pretty much all you'll be able to fit it.

How is it made?

The compact layout is simply a Grid layout, but where the individual items use a compact document card layout instead of the full document card.

To create your own, you would use an Office UI Fabric List control and render each item inside the list with a DocumentCard control where the type of the DocumentCard control would be set to DocumentCardType.compact.

Or, you can simply use the component I built to make my life easier.

How to create a web part with the compact layout

Note: The source code for this sample can be found in the WebPartLayouts sample on my repo.

  1. Create your own web part solution. For this sample, we'll assume that your web part is called CompactWebPart and that the component which renders the content of the web part is called Compact.

  2. Copy the content of the src\components\compactLayout from my sample code to your own solution. You may need to create a components and compactLayout folder under src to do so.

  3. In your web part, load the items you wish to display. For this example, we'll set them in your web part's state and hard-code them in your web part's constructor, but feel free to load any data you want to use. We'll want to have at least a title attribute and a thumbnail attribute, but -- as you'll see later -- you can really display any information you want when you render each item.

    constructor(props: ICompactProps) {
    super(props);
    
    // Sample data generated at https://mockaroo.com/
    this.state = {
      items: [{
        thumbnail: "https://robohash.org/nostrumquiiure.png?size=48x48&set=set1",
        title: "Aerified"
      }, {
        thumbnail: "https://robohash.org/minimafugitenim.png?size=48x48&set=set1",
        title: "Viva"
      }, {
        thumbnail: "https://robohash.org/nihilbeataeculpa.png?size=48x48&set=set1",
        title: "Overhold"
      }, {
        thumbnail: "https://robohash.org/essequiquo.png?size=48x48&set=set1",
        title: "Latlux"
      }, {
        thumbnail: "https://robohash.org/inipsumtotam.png?size=48x48&set=set1",
        title: "Biodex"
      }, {
        thumbnail: "https://robohash.org/utmodiet.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/undeenimvel.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/pariaturoditdolore.png?size=48x48&set=set1",
        title: "Opela"
      }, {
        thumbnail: "https://robohash.org/nullaullamincidunt.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/accusantiumnonvoluptatibus.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/culpaeossapiente.png?size=48x48&set=set1",
        title: "Sonsing"
      }, {
        thumbnail: "https://robohash.org/harumnihilvelit.png?size=48x48&set=set1",
        title: "Duobam"
      }, {
        thumbnail: "https://robohash.org/quianesciuntet.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/aliquidipsamrem.png?size=48x48&set=set1",
        title: "Keylex"
      }, {
        thumbnail: "https://robohash.org/dignissimoseosaccusamus.png?size=48x48&set=set1",
        title: "Span"
      }, {
        thumbnail: "https://robohash.org/exomnisexcepturi.png?size=48x48&set=set1",
        title: "Stringtough"
      }, {
        thumbnail: "https://robohash.org/occaecatimolestiaererum.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/consequaturinquis.png?size=48x48&set=set1",
        title: "Alpha"
      }, {
        thumbnail: "https://robohash.org/sapienteofficiisest.png?size=48x48&set=set1",
        title: "Job"
      }, {
        thumbnail: "https://robohash.org/similiquesuntiusto.png?size=48x48&set=set1",
        title: "Cookley"
      }, {
        thumbnail: "https://robohash.org/sitnequequi.png?size=48x48&set=set1",
        title: "Stronghold"
      }]
    };
    }
  4. In your web part's component, add an import for the compactLayout. You'll also need an import for the UI Fabric DocumentCard component:

    // Used to render document cards
    import {
    DocumentCard,
    DocumentCardPreview,
    DocumentCardDetails,
    DocumentCardTitle,
    IDocumentCardPreviewProps,
    DocumentCardType
    } from 'office-ui-fabric-react/lib/DocumentCard';
    import { ImageFit } from 'office-ui-fabric-react/lib/Image';
    import CompactLayout from '../../../components/compactLayout/CompactLayout';
  5. Add a _onRenderGridItem method:

    private _onRenderGridItem = (item: any, _index: number): JSX.Element => {
    const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.centerCover,
          height: 48,
          width: 48
        }
      ]
    };
    
    return <div
      data-is-focusable={true}
      data-is-focus-item={true}
      role="listitem"
      aria-label={item.title}
    >
      <DocumentCard
        type={DocumentCardType.compact}
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)}
      >
        <DocumentCardPreview {...previewProps} />
        <DocumentCardDetails>
          <DocumentCardTitle
            title={item.title}
            shouldTruncate={true}
          />
        </DocumentCardDetails>
      </DocumentCard>
    </div>;
    }
    }
  6. Update your component's render method to render the compact layout as follows:

    public render(): React.ReactElement<ICompactProps> {
    return (
      <div className={styles.compact}>
        <CompactLayout
          items={this.state.items}
          onRenderGridItem={(item: any, index: number) => this._onRenderGridItem(item, index)}
        />
      </div>
    );
    }

That's really all there is to it! You web part will render something like this:
The compact layout

The great thing is that it is entirely up to you how you want to render each item. I chose to use the DocumentCard control, but you can replace any part of your _onRenderGridItem method to suit your needs.

For example, if you wanted to render a date instead of an thumbnail, you could use something like the DateBox component that I wrote for the React Calendar Feed sample.

The Datebox control

Adding pagination

When I first built the React Calendar Feed sample, the Events web part showed little Previous and Next buttons at the bottom of the web part when displaying in the compact mode.

It seems that the pagination has since been removed from the standard web part design. Nevertheless, I have included a sample control to show how you can add pagination to your compact web part.

Use it at your discretion.

To add pagination to your web part, follow these steps:

  1. Copy the content of the src\components\paging from my sample code to your own solution. You may need to create a paging folder under src\components to do so.
  2. Add a variable to store the current page number in your component's state, as follows:
    export interface ICompactState {
    items: any[];
    currentPage: number;
    }
  3. In your component's constructor, set the current page to 1, as follows:

    constructor(props: ICompactProps) {
    super(props);
    
    // Sample data generated at https://mockaroo.com/
    this.state = {
      currentPage: 1,
      items: [{
        thumbnail: "https://robohash.org/nostrumquiiure.png?size=48x48&set=set1",
        title: "Aerified"
      }, {
        thumbnail: "https://robohash.org/minimafugitenim.png?size=48x48&set=set1",
        title: "Viva"
      }, {
        thumbnail: "https://robohash.org/nihilbeataeculpa.png?size=48x48&set=set1",
        title: "Overhold"
      }, {
        thumbnail: "https://robohash.org/essequiquo.png?size=48x48&set=set1",
        title: "Latlux"
      }, {
        thumbnail: "https://robohash.org/inipsumtotam.png?size=48x48&set=set1",
        title: "Biodex"
      }, {
        thumbnail: "https://robohash.org/utmodiet.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/undeenimvel.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/pariaturoditdolore.png?size=48x48&set=set1",
        title: "Opela"
      }, {
        thumbnail: "https://robohash.org/nullaullamincidunt.png?size=48x48&set=set1",
        title: "Rank"
      }, {
        thumbnail: "https://robohash.org/accusantiumnonvoluptatibus.png?size=48x48&set=set1",
        title: "Bitchip"
      }, {
        thumbnail: "https://robohash.org/culpaeossapiente.png?size=48x48&set=set1",
        title: "Sonsing"
      }, {
        thumbnail: "https://robohash.org/harumnihilvelit.png?size=48x48&set=set1",
        title: "Duobam"
      }, {
        thumbnail: "https://robohash.org/quianesciuntet.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/aliquidipsamrem.png?size=48x48&set=set1",
        title: "Keylex"
      }, {
        thumbnail: "https://robohash.org/dignissimoseosaccusamus.png?size=48x48&set=set1",
        title: "Span"
      }, {
        thumbnail: "https://robohash.org/exomnisexcepturi.png?size=48x48&set=set1",
        title: "Stringtough"
      }, {
        thumbnail: "https://robohash.org/occaecatimolestiaererum.png?size=48x48&set=set1",
        title: "Prodder"
      }, {
        thumbnail: "https://robohash.org/consequaturinquis.png?size=48x48&set=set1",
        title: "Alpha"
      }, {
        thumbnail: "https://robohash.org/sapienteofficiisest.png?size=48x48&set=set1",
        title: "Job"
      }, {
        thumbnail: "https://robohash.org/similiquesuntiusto.png?size=48x48&set=set1",
        title: "Cookley"
      }, {
        thumbnail: "https://robohash.org/sitnequequi.png?size=48x48&set=set1",
        title: "Stronghold"
      }]
    };
    }
  4. Add an import for the paging component at the top of your file:
    import { Paging } from '../../../components/paging';
  5. Change your render method to get a subset of items to show, as follows:

    public render(): React.ReactElement<ICompactProps> {
    let pagedItems: any[] = this.state.items;
    const totalItems: number = pagedItems.length;
    let showPages: boolean = false;
    const maxEvents: number = 5; // Use any page size you want
    const { currentPage } = this.state;
    
    if (true && totalItems > 0 && totalItems > maxEvents) {
      // calculate the page size
      const pageStartAt: number = maxEvents * (currentPage - 1);
      const pageEndAt: number = (maxEvents * currentPage);
    
      pagedItems = pagedItems.slice(pageStartAt, pageEndAt);
      showPages = true;
    }
    
    return (
      <div className={styles.compact}>
        <CompactLayout
          items={pagedItems}
          onRenderGridItem={(item: any, index: number) => this._onRenderGridItem(item, index)} />
    
        {showPages &&
          <Paging
            showPageNumber={true}
            currentPage={currentPage}
            itemsCountPerPage={maxEvents}
            totalItems={totalItems}
            onPageUpdate={this._onPageUpdate}
            nextButtonLabel={strings.NextLabel}
            previousButtonLabel={strings.PreviousLabel}
          />
        }
      </div>
    );
    }
  6. Finally, add a method to store the current page number in your state every time the page changes, as follows:
    private _onPageUpdate = (pageNumber: number): void => {
    this.setState({
      currentPage: pageNumber
    });
    }

Test your web part. You should now get a next and previous buttons at bottom of your web part.

Compact pagination

Conclusion

The compact layout is simply a grid control which renders compact document cards.

In our next post, we'll discuss the list layout!

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

In our last post we discussed the filmstrip layout.

In today's post, we'll continue our discussion about the web part layout patterns and discuss the carousel layout.

What is the carousel

The carousel is a standard web part layout that presents your web part content, showing items one at a time.

A demo of the carousel layout

The carousel is best suited when you want to showcase the content instead of the metadata about the content. For example, if you want to show a large preview of your documents or a large picture.

By default, the carousel will expand your images to fit the whole width of your web part and maintain the aspect ratio of your images, with a minimal amount of metadata below the image (for example, the file name and location). It also shows how many items there are in the carousel and which "slide" you're currently viewing.

As your cursor approaches the carousel, two buttons appear, providing the users with the options to go to the next and/or the previous slide. The buttons are positioned halfway down the carousel, one button on each side of the image.

On touch-enabled devices, such as phones and tablets, users can also swipe left of right to indicate whether they like the image to move to the next/previous slide.

After the user has viewed all images, the carousel restarts with the first images, potentially keeping your users entertained for hours and hours.

The problem with carousels

If you mention carousels to most designers, they'll often groan and sigh heavily. That's because the carousel quickly became a trend that everybody wanted on their web sites without really understanding the impact on their users.

If you ask me, there were worse trends out there (like when suddenly everyone decided that you needed to enter your email address and then confirm your email, presumably to fool someone who was trying to enter a fake email address to give your the right email address the second time...? )

I already discussed the issues with carousels when I covered the filmstrip, but here is a brief summary:

  • Their design and frequent movement make people think that they are an ad, causing banner blindness.
  • Moving elements can hurt accessibility, especially for users with motor skill issues
  • People with cognitive difficulties can have difficulty reading all the text before the carousel moves to the next slide
  • Because they see only one slide at a time, users often miss content

The SharePoint carousel: the kinder, gentler carousel

Fortunately, the SharePoint carousel is better designed and less evil.

For one, it does not automatically scroll. It puts users in control of the navigation.

It also supports keyboard navigation and gestures, making it slightly easier for those with accessibility requirements to use the carousel.

It is designed to keep the amount of information on each slide simple (instead of overloading users with too much information)

How to use a carousel properly

Here are some tips for a better carousel design (backed by science!!!!):

  • Keep your slides to fewer than 5: It can be frustrating to swipe through too many items in a carousel. Also your users may not be able to keep a mental list of more than 5 items and may not recognize when they're seeing the same items over and over again. Keeping your items to fewer than 5 the number helps users discovering content, and makes it easy for them to find content again later.
  • Show how many items there are and which slide is currently displayed: It helps users understand that there are more items and helps them feel "in control".
  • Try to show items that are related: Because users only get to see one item at a time, stick to items that are related so that users can easily predict what the other items (the ones they can't see) are.

And probably the most important: make each slide a giant button. It's Fitts's law!

Fitts's law

In 1954 -- long before there were carousels and web sites -- Paul Morris Fitts was researching the human motor system. I'll spare you the boring math, but what he found is this:

  • The greater the distance between your starting point and your target, the harder it is to hit a target
  • The smaller the target is, the harder it is to hit
  • The faster you move from your starting point to your target, the harder it is to hit.

Nowadays, Fitts's law is used in user experience and user interface design. It is the reason why the menu on your Mac computer is located at the top of the screen without a top border (essentially making the menu a button of infinite size), and why Windows 8 tried to use the edges of your screen for the start menu, the share menu, and the context menus.

For example, here is how the distance between two buttons affect how easy it is to hit the target:
Further distance is hard to hit

And here is how size affects how easy it is to hit a target:

The reason why the SharePoint Framework web part layouts (the grid, the filmstrip, the carousel and the compact layout) mostly use large rectangular targets is not just because it looks cool, it is also because it makes it easier for users to hit the targets, regardless of where they started from on the page.

That's why you need to make sure that your entire carousel slide is a giant button that will lead to whatever item you're currently displaying.

Unfortunately, I still think that the next and previous buttons (and the little dots on the filmstrip layout) are a little too small, but since the layout controls support keyboard, and touch gestures, I'll stop complaining now.

For more information about Fitts's law, check out this cool interactive visualization

How carousel control is implemented

As is the case with the filmstrip control, the out-of-the-box carousel layout uses Ken Wheeler's Slick slider -- not just to provide the previous/next functionality, but to control how each slide is resized to fit within the viewable area.

The control I created for this sample aims to mimic the exact functionality of the out-of-the-box SharePoint carousel layout. It also uses the Slick slider. (Hey, if it's good enough for the SharePoint team, it's good enough for me). It the one I wrote for the React Calendar Feed sample in June 2018.

However, it is worth noting that Piotr Siatka recently submitted a carousel control to the Reusable React controls for SharePoint Framework solutions and I can't wait for it to become available in an upcoming version of the PnP controls. He actually built it without any third-party libraries and it looks really cool. I encourage you to check out his awesome work.

Piotr's awesome carousel control
Piotr's carousel control, coming soon to a PnP library near you

When it will become available, Piotr's control will give you the ability to control most aspects of carousel appearance and behavior.

My carousel layout control is designed to make it easy to create a web part with that mimics the carousel layout. It doesn't give you control over how the arrows look, where they are located, or anything else that deviates from the carousel layout standard.

Feel free to use whichever control is most suitable for you.

To create a carousel layout web part

To create your own web part using the carousel layout, follow these steps:

  1. Copy the folder src/components/carouselLayout from my sample code to your own web part project.
  2. In your src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx, add an import for the CarouselLayout and the CarouselSlide controls.
    import { CarouselLayout, ICarouselItem } from '../../../components/carouselLayout';
  3. In your web part's component, retrieve the items you wish to display and store them where you can retrieve them later. For example, it your web part's state. For this example, I'll store sample items in the constructor:

    constructor(props: ICarouselProps) {
    super(props);
    
    this.state = {
      items: [{
        imageSrc: "https://lorempixel.com/744/418/technics/1/",
        title: "Adventures in SPFx",
        location: "SharePoint",
      }, {
        imageSrc: "https://lorempixel.com/744/418/technics/2",
        title: "The Wild, Untold Story of SharePoint!",
        location: "SharePoint",
      }, {
        imageSrc: "https://lorempixel.com/744/418/technics/4",
        title: "Not Your Grandpa's SharePoint",
        location: "SharePoint",
      }, {
        thumbnail: "https://lorempixel.com/744/418/technics/5/",
        title: "Get with the Flow",
        location: "Flow",
      }]
    };
    }

    This is pretty much the same thing we've done in all web part layouts samples so far, except that this time the items we pass must follow the ISlideItem interface and provide a title, a imageSrc, and a location (that's all the carousel has room to display).

  4. In the onRender method, add a CarouselLayout object and pass it the items you wish to display. You'll also need to provide a pagingTemplate string, which is template to control how you want to render the "1 of 5" items label. In your pagingTemplate, use a 0 for the current item placeholder, and 1 for the number of items placeholder.

    public render(): React.ReactElement<ICarouselProps> {
    
    return (
      <div className={styles.carousel}
    
      >
        <CarouselLayout
          pagingTemplate={'{0} of {1}'}
          ariaLabel={'Use right and left arrow keys to navigate between images in the carousel. Use up and down arrow keys to access the edit and remove buttons for any image.'}
          items={this.state.items}
          onSlideClick={(currentSlide) => { alert(`You clicked on slide ${currentSlide+1}`); }}
        >
        </CarouselLayout>
      </div>
    );
    }

    You should also provide an ariaLabel for accessibility's sake, and you must pass an event handler to handle when a user clicks on a slide.

If all goes well, you'll render the following carousel:

The end result

Conclusion

I'm not crazy about the fact that the carousel layout control I used in this sample isn't as flexible as the previous controls, but that's because all we can display is an image, a title and a location.

On the other hand, it makes it pretty easy to implement your own carousel layout web part.

The code for this sample can be found in my WebPartLayouts solution on my repo.

We're almost done with the layouts! Next stop: the compact layout.

Until then, let me know if you have any questions, comments, or concerns!

Note: There are a lot of screenshots in this post. Here's why: there is nothing that I dislike more than having to download/install something to find out exactly what's inside. Yes, there are a lot of screenshots, but you'll know exactly what to expect if you decide to download my PowerPoint template.

Introduction

As the self-proclaimed World's Laziest Developer, I always look for shortcuts and ways to avoid doing work.

When I start a SharePoint project where I have to design an Information Architecture, I like to use low fidelity wireframes instead of spending countless hours creating SharePoint sites.

I'm not a designer or a user experience specialist, but I'll take the time to do paper napkin drawings, whiteboard drawings, and any other available tools to help work through how my sites are going to be structured and laid out.

I'll even use tools like Balsamiq, UXPin, Adobe XD, and Visio, but I like to spend as little time as possible building my low-fidelity wireframes. Also, I find that few people have experience with those tools (even though they are awesome).

I do it because it is faster convey to my stakeholders what I'm planning on doing and getting feedback with a minimum effort. And I'm all about minimum efforts.

Since PowerPoint now has sketchy shapes, I decided to create a PowerPoint template that contains a sketchy design for SharePoint pages and a few of the commonly used web parts.

My template is not intended to replace the SharePoint Toolkit, but it requires Adobe XD and most client workstations I use do not have Adobe XD. If you can use Adobe XD and the SharePoint Toolkit, do so. If you can't feel free to use my PowerPoint template.

If you want to use it, you can download it and use it as you wish.

Note: This template will only work in the desktop version of PowerPoint. And it may require the Office Insider version of PowerPoint.

A few rules, though:

  • If you add more page designs and web parts that you think other people would enjoy, share it with me and I'll add it to the template
  • Don't go selling this template
  • Share it with others (#SharingIsCaring)

The components

Here are the elements you can use for now:

The Communication Site master

Communication site master

There is also a read-only version of the communication site if you want to show what the experience looks like for visitors:

Communication site read-only

Guided layouts

If you choose a slide master, you can pick a layout to help you place your web parts on the page.

1 column guide
1 column guide

2 column guide
2 column guide

3 column guide
3 column guide

Simply place your web parts so that they fit within the gray area. Once you're done moving the web parts, select the No guide version, which hides all the guides.

Hero web part

Hero web part

(I had a lot of fun drawing the placeholder pictures. Very therapeutic.)

News web part

News web part

Events web part

Events

Documents web part

Documents web part

People web part

People web part

Generic grid layout web part

If you want to write your web part with the grid layout and would like to mock them up in your wireframes, you can use the Generic Grid Layout Web Part. It comes in 1/3 column, 2/3 columns, and full-width variations.

Generic Grid Layout Web Part -- Full width
Generic Grid Layout Web Part -- Full width

Generic Grid Layout Web Part -- 2/3 columns
Generic Grid Layout Web Part -- 2/3 columns

Generic Grid Layout Web Part -- 1/3 column
Generic Grid Layout Web Part -- 1/3 column

Generic filmstrip layout web part

If you want to write your web part with the filmstrip layout and would like to mock them up in your wireframes, you can use the Generic Filmstrip Layout Web Part. It comes in 1/3 column, 2/3 columns, and full-width variations.

Generic Filmstrip Layout Web Part -- Full width
Generic Filmstrip Layout Web Part -- Full width

Generic Filmstrip Layout Web Part -- 2/3 columns
Generic Filmstrip Layout Web Part -- 2/3 columns

Generic Filmstrip Layout Web Part -- 1/3 column
Generic Filmstrip Layout Web Part -- 1/3 column

Generic carousel layout web part

If you want to design a custom web part that uses the carousel layout, use this template. Like the other generic web parts, it comes in 3 flavours: Full-Width, 2/3 columns and 1/3 column.

Generic Carousel Web Part -- Full Width
Generic Carousel Web Part -- Full Width

Generic Carousel Web Part -- 2/3 columns
Generic Carousel Web Part -- 2/3 columns

Generic Carousel Web Part -- 1/3 column
Generic Carousel Web Part -- 1/3 column

Generic list layout web part

I couldn't do the other generic layouts and omit the list layout! This one is also available in Full Width, 2/3 columns and 1/3 column.

Generic List Web Part -- Full Width
Generic List Web Part -- Full Width

Generic List Web Part -- 2/3 columns
Generic List Web Part -- 2/3 columns

Generic List Web Part -- 1/3 column
Generic List Web Part -- 1/3 column

Color palettes

Although many believe that low-fidelity wireframes should use grayscale and contrast instead of colors, sometimes I like to include colors. It really depends on my mood (and the audience).

Whether you're in the "no-colors" camp or in the "with colors" camp, every slide includes the SharePoint color palettes on the right of the page -- outside of the viewable area.

SharePoint color palettes

When you're editing your pages, you can pick the Eyedropper tool to pick the colors you need from the palette.
Eyedropper tool

Don't worry: because they are outside of the viewable area, the color palettes won't show up while you're presenting or when you print.

Color palettes won't print

If you need to find out more about what each color is, there is a section at the end of the PowerPoint template that explains the SharePoint colors.

SharePoint Colors Slide

Other stuff

There is also some token lorem ipsum placeholder text in case you need it, but I try to avoid it.

Also, there are sticky notes to help annotate your wireframes.

Sticky notes and wireframes

What else would you like to see? Let me know in the comments.

Using the template

Here are simple tips to use the template:

  • The template uses an 11"x17" page template. It may seem big, but it prevents you from having to deal with 4pt fonts. Don't worry, it presents well on a screen, and prints great posters for your team project room walls.
  • It uses the new sketchy line styles. It may not work in the web version of PowerPoint or if you don't have the Office Insider edition of PowerPoint. Let me know if you need a non-sketchy version and I'll try to oblige. If you get an error opening it in the web browser, try opening it on your desktop
    Error message with PowerPoint
    If you get this message, it is most likely because I used sketchy lines
  • To emphasize on the "work in progress" look, I used the Segoe Print font because I found that it was available on most workstations that I use. If you don't have this font, feel free to use any font you like. Ink Draft and Segoe Marker work well too. Just resist the urge to use Comic sans !
  • For now, every slide uses the communication site layout (and the read-only variation). I'll add the team site layout if I get requests to do so
  • The title of the slide is the site title
    Site title is the slide title
  • Every site element is grouped to make it easier to move them around. Feel free to un-group them and edit them as you need
    Grouping within the slide
  • I used some theme colors, but feel free to make the designs monochromatic if you want
  • If you need to edit the navigation, feel free to copy the navigation elements (called Site Navigation ) from the master slide and paste the customized navigation on your slide. It already has a white background to hide the navigation from the master slide
    Site Navigation

To edit site navigation

To edit the site navigation, follow these steps:

  1. From your PowerPoint slide, go to View then select Slide Master in the Master Views group
    Slide Master from the View ribbon
  2. PowerPoint should automatically take you to the master slide called Communication site. Select the site navigation by clicking on the site icon and click Copy.
    Copy site navigation
  3. From the Slide Master menu, select Close Master View to go back to your slide
    Close Master View
  4. Paste the navigation on top of the existing navigation. The white background behind the site navigation you just copied should hide the existing one from the master slide.
  5. Edit the navigation and site icon as you wish

To create your own wireframe

Although I have a sample wireframe in my template, you can create your own using these simple steps:

  1. In PowerPoint, insert a new slide in your presentation by going to New Slide and select the layout you want
    New Slide Menu
    You can pick from the Communication Site -- Edit Mode to show what an author would see, or Communication Site -- Read-only Mode to show what a visitor would see.
  2. Try to start with one of the guides. You can choose from 1 column guide, 2 column guide or 3 column guide.
  3. From the Web Part Templates section, find the web parts you want from the other slides in the PowerPoint template, copy them and paste them onto your new slide.
  4. Make sure to place the web parts within the gray areas on the guides.
  5. Edit the web part elements (like web part title and content). It may help to ungroup them first.
  6. Once you're done placing your web parts, go to Layout and pick the No guide version of whatever master you chose. It will remove the background guides, but will not affect where you placed your web parts.
    Layouts

Tips when preparing low fidelity wireframes

As Page Laubheimer from the Nielsen Norman Group puts it:

Sharing low fidelity user-interface prototypes with stakeholders is a great way to transfer knowledge and get buy-in early.

Tell your audience that this is work in progress. It may be obvious to you, but I have had clients get upset that their site was going to look "all wobbly" (and others who asked if the site was going to only be "in French" because I used lorem ipsum). Explain that you used this style of wireframe to help focus on the content and structure, not the look.

Explain to your audience that this is a great opportunity to share knowledge: for them to transfer their knowledge to you by getting their feedback early.

Don't worry about the look and feel, worry about the content/structure and function. Resist the urge to put lorem ipsum and try to put some text that is as real as you can make it for now.

Another way to use these wireframes is to print them and get your audience to mark them with their notes. You'd be amazed by what information you can get from watching people circle, underline, and annotate paper wireframes.

Conclusion

My PowerPoint template is available for you to use.

I'll continue adding web parts and designs. If you need anything else added to it, let me know in the comments.

Maybe I'll create a higher-fidelity version of these if people like them. I'd also love to create a version that you can print out and cut so that you can layout page designs on paper.

Let me know what you think?

Updates

  • August 5, 2019: Thank you Vesa Juvonen for pointing out the broken link. Moved files to GitHub repo to make it easier for those of you who wish to contribute.
  • August 3, 2019: Added more web parts (List, carousel, filmstrip) and explanation why I have so many damned screenshots. Also added introduction how to use the guides and section explaining color palettes.
  • August 2, 2019: Added more web parts (generic grid 1/3, 2/3, and full width) and guide layouts

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

In our last post we discussed layout patterns and showed how to create a Grid Layout web part.

In today's post, we'll continue our discussion about the web part layout patterns.

The Filmstrip layout

Like the grid layout, the filmstrip displays cards to display content. You can use any other rectangular content though. For example, you can use images of kittens, or event cards (as I did with my Calendar Feed web part).

Calendar feed web part
Calendar feed web part switches between filmstrip and compact mode

However, unlike the grid layout, the filmstrip displays items on a single row. As the screen resizes, the filmstrip layout resizes the items to keep the same number of items displayed at once. If there are more items than can be shown at once, the filmstrip will change to a carousel and break items into "pages".

Filmstrip layout resizing items as the screen resizes
The filmstrip layout resizes items as the page resizes

If you're a web designer, the word carousel may make you shudder. In fact, if you search online, you'll find over fifty-four million search results explaining why carousels are bad.

Banner blindness

You see, most people have issues with the giant photo carousels on web sites. For most users, the giant photos trigger our banner blindness because we automatically assume it is an advertisement, and we ignore it.

Since the carousel in the filmstrip layout does not show a giant image, but a series of smaller images (usually a preview of documents), this isn't something we have to worry about (although we'll have to have another conversation when we discuss the Carousel layout).

Mobile issues

Another issue is that giant carousels are notoriously bad on mobile devices.

One of the issues with carousels on mobile devices is that because of limited screen real-estate, it shows only one item at a time. This forces users to use sequential access: to view items in the order that is dictated by the control, not by the user's choice.

Research shows that -- with sequential access -- users will stop looking at items after the third or fourth item in a carousel.

Another issue with carousels that people have is with discoverability. If someone is in a hurry and they quickly glance at a typical carousel, they may not know that there are more items to see. Most carousels will use a teeny tiny little dot (which are often a bad design choice) to indicate that there are more items.

Fortunately, the mobile version of the filmstrip control uses bigger dots that are as big as the main text with a lot of colour contrast to help.

Sample mobile carousel
Mobile filmstrip. This isn't so bad?

If you plan on using the filmstrip layout, follow these guidelines:

  • Limit your items to less than 5: Otherwise, people may not see the other items.
  • Prioritize your items: Keep in mind that, depending on the screen size, some users may only see one item at a time. Make sure that you place the most important items first.
  • Make sure items are related: Because people may not see all items in your filmstrip, make sure that the items you show on the filmstrip are related so that users can predict/easily guess what items are hidden. In other words, don't put apples and oranges on the filmstrip.

Accessibility

For me, the biggest concern about most carousels has to do with accessibility.

You see, most evil carousels break a lot of accessibility guidelines:

  • The dots at the bottom are often too small to see and/or click
  • The dots often have a poor colour contrast
  • The slides often change automatically, without giving users a chance to read the content or slow it down
  • The left/right arrows often rely on the use of a mouse, with no keyboard alternatives
  • Left/right arrows often have poor colour contrasts
  • Carousels often do not provide alternative text

Luckily for us, the SharePoint filmstrip (and the carousel layout) doesn't have many of the typical carousel accessibility issues:

  • The slides do not change automatically
  • Users can navigate through the slides using the and keys on their keyboard and use TAB to cycle through the slides.
  • The left/right navigation arrows use a high contrast so that users with colour blindness and/or various forms of visual impairment can see them
    Left/right arrows have high contrast
  • The left/right navigation arrows aren't too small (but they could be bigger)
  • The filmstrip provides alternative text for the entire filmstrip (e.g.: "Highlighted content web part, showing Most recent Documents., Use right and left arrow keys to navigate between cards in the film strip."), as well as alternative text for every item in the filmstrip.
  • The bullets at the bottom of the filmstrip are still pretty small, but they have high contrast (21:1 on the theme I tested) exceeding the minimum required contrast (4.5:1)

It still isn't perfect, but it isn't as bad as most carousel controls out there.

How the filmstrip layout is implemented

Behind the scenes, the out-of-the-box SharePoint filmstrip and carousel layouts use Ken Wheeler's awesome Slick carousel.

Slick carousel
The slick carousel as a filmstrip

They simply made it more "Fabric UI" and fixed some accessibility issues.

Unlike the grid layout, which calculates the optimal card size as you resize the page, the filmstrip layout uses breakpoints to use pre-determined settings depending on the dimensions of the filmstrip control.

How to use the filmstrip layout

Unfortunately -- as was the case with the grid layout -- there aren't any ready-to-use controls to create a filmstrip layout web part.

I had to create my own filmstrip layout when I created the Calendar Feed web part for the SharePoint/sp-dev-fx-webparts repo.

React Calendar Feed Web Part with filmstrip
Custom filmstrip control in the calendar web part

And since I'm the world's laziest developer and I hate to write things more than once, I had created the filmstrip component as a re-usable component. (Ok, I had called it CarouselContainer back then, but it was really a filmstrip layout control).

For the rest of this post, we'll use my filmstrip layout control. If you don't like the one I created, feel free to use your own.

As we did for the grid layout, I'll show you how to insert a copy of my control into your own web parts -- that way, you can customize it to suit your own needs.

(Don't worry, I'll show you how to move the controls into a component library in a later post, so you won't have to always copy and paste my code into your solutions)

To create your own filmstrip layout web part, follow these steps:

  1. Create a web part solution with the Yeoman generator. For this sample, I'll call my web part filmstrip
  2. Copy the src\components\filmstripLayout folder from my project to yours
  3. In your project's terminal, install the slick-carousel and react-slick dependencies by using the following command:
    npm i slick-carousel react-slick
  4. In your web part's component, located at src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx, add an import for the filmstripLayout control:
    import { FilmstripLayout } from '../../../components/filmstripLayout';
  5. In your code, load the items you wish to display. You can load an array of any items you wish, but for my sample, I used items with thumbnail, title, name, profileImageSrc, location, and activity props because I want to use the DocumentCard control to render my list items. For my sample, I initialized the state with a static list of items in my component's constructor:

    constructor(props: IFilmstripProps) {
    super(props);
    
    this.state = {
      items: [{
        thumbnail: "https://lorempixel.com/400/200/technics/1/",
        title: "Adventures in SPFx",
        name: "Perry Losselyong",
        profileImageSrc: "https://robohash.org/blanditiisadlabore.png?size=50x50&set=set1",
        location: "SharePoint",
        activity: "3/13/2019"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/2",
        title: "The Wild, Untold Story of SharePoint!",
        name: "Ebonee Gallyhaock",
        profileImageSrc: "https://robohash.org/delectusetcorporis.bmp?size=50x50&set=set1",
        location: "SharePoint",
        activity: "6/29/2019"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/3",
        title: "Low Code Solutions: PowerApps",
        name: "Seward Keith",
        profileImageSrc: "https://robohash.org/asperioresautquasi.jpg?size=50x50&set=set1",
        location: "PowerApps",
        activity: "12/31/2018"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/4",
        title: "Not Your Grandpa's SharePoint",
        name: "Sharona Selkirk",
        profileImageSrc: "https://robohash.org/velnammolestiae.png?size=50x50&set=set1",
        location: "SharePoint",
        activity: "11/20/2018"
      }, {
        thumbnail: "https://lorempixel.com/400/200/technics/5/",
        title: "Get with the Flow",
        name: "Boyce Batstone",
        profileImageSrc: "https://robohash.org/nulladistinctiomollitia.jpg?size=50x50&set=set1",
        location: "Flow",
        activity: "5/26/2019"
      }]
    };
    }
  6. In your render method, add a filmstripLayout component. You should set the ariaLabel prop, but it is optional.

    public render(): React.ReactElement<IFilmstripProps> {
    return (
      <div className={styles.filmstrip}>
        <FilmstripLayout 
                ariaLabel={"Sample filmstrip layout web part, showing sample items., Use right and left arrow keys to navigate between cards in the film strip."}
    
        >
    
        </FilmstripLayout>
      </div>
    );
    }
  7. To render elements within your filmstrip layout, you simply add rectangular elements as children of the FilmstripLayout component. In my sample, I'll use a DocumentCard to render each item. To do so, you'll need to add the following imports:
    // Used to render document cards
    import {
    DocumentCard,
    DocumentCardActivity,
    DocumentCardPreview,
    DocumentCardDetails,
    DocumentCardTitle,
    IDocumentCardPreviewProps,
    DocumentCardLocation,
    DocumentCardType
    } from 'office-ui-fabric-react/lib/DocumentCard';
    import { ImageFit } from 'office-ui-fabric-react/lib/Image';
  8. Then, loop through your items and render a DocumentCard in your render method:

    public render(): React.ReactElement<IFilmstripProps> {
    return (
      <div className={styles.filmstrip}>
        <FilmstripLayout
        ariaLabel={"Sample filmstrip layout web part, showing sample items., Use right and left arrow keys to navigate between cards in the film strip."}
        >
          {this.state.items.map((item: any, _index: number) => {
            const previewProps: IDocumentCardPreviewProps = {
              previewImages: [
                {
                  previewImageSrc: item.thumbnail,
                  imageFit: ImageFit.cover,
                  height: 130
                }
              ]
            };
    
            return <div
              className={styles.documentTile}
              data-is-focusable={true}
              role="listitem"
              aria-label={item.title}
            >
              <DocumentCard
                type={DocumentCardType.normal}
                onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert("You clicked on an item")}
              >
                <DocumentCardPreview {...previewProps} />
                <DocumentCardLocation location={item.location} />
                <DocumentCardDetails>
                  <DocumentCardTitle
                    title={item.title}
                    shouldTruncate={true}
                  />
                  <DocumentCardActivity
                    activity={item.activity}
                    people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
                  />
                </DocumentCardDetails>
              </DocumentCard>
            </div>;
          })}
        </FilmstripLayout>
      </div>
    );
    }

If I didn't forget anything, you should see a web part that looks like this:

Custom filmstrip in action

Conclusion

The filmstrip layout is a great way to show more items than available screen, but keep in mind that there are accessibility/usability issues with it.

If you want to build a filmstrip layout web part, you can use my code sample to get started.

In our next post, we'll continue exploring the web part layout design patterns.

Photo Credits

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

In today's post, we'll begin discussing the web part layout patterns.

SharePoint web part layouts

If you look at the various web parts that are available out-of-the-box on SharePoint, you'll find that there are many different layouts for web parts.

In fact, there are 5 commonly-used layouts:

  • Grid
  • Filmstrip
  • List
  • Carousel
  • Compact

Five common web part layouts
5 common web part layouts

Each layout is best suited for different uses.

How to decide which layout to use

When planning your web part design, you can pick the best layout by considering the following criteria:

  • How "visual" is the content: does the content consist of a picture or a document preview (more visual), or is it mostly text (less visual)?
  • How much metadata to display: Do you need to simply provide a title, author, date and maybe a category (like most out-of-the-box web parts do) or do you need to provide more metadata?
  • Number of items to display: Do you want to display only a few items, or do you need to display many items at once.

To help the decision process, I use the following matrix which places the standard layouts against a grid of how visual and how much metadata you wish to display. The size of each circle helps to compare how many items you want to display -- the bigger the circle, the more items you can show.

Web part layout decision matrix

You should also consider how much space you'll have on a page and the size of the page.

"But I don't have control over the content and size of the page!"

...I can already hear some of you say. You're absolutely right! That's why you should consider giving your authors a few layout options so that they can pick the layout that is best suited for their page designs.

Luckily, you don't have to write multiple versions of the same code to offer multiple layouts; Most of the layouts re-use the same/similar components.

For example:

  • The Grid layout and the Filmstrip layout are essentially the same, except that the Grid wraps content over multiple rows, where the Filmstrip keeps items on a single row.
  • The Filmstrip turns into a Carousel when there are more items to show that what can be displayed on a single screen.
  • The Grid turns into a Compact layout when the screen is too small

The easiest way to demonstrate this is probably with code!

Grid layout

The grid layout presents content in rectangular areas in rows and columns from left to right and top to bottom. The grid layout will attempt to fit as many columns as possible and resize the grid items, or individual content elements within the grid, to fit the entire width of the grid.

When the grid resizes, it re-flows the grid items by keeping the same number of columns but making each column narrower. When the columns become too narrow, the grid will remove one column and resize the remaining columns to fit within the grid.

When there is only room for 1 column within the grid (e.g.: when viewed on a mobile device or if the web part is located in a one-third column), the grid layout will change to a compact layout.

Grid layout re-flowing contnet
The grid layout re-flows content in rows and columns

Creating a grid layout web part

Unfortunately, there isn't a readily-usable grid layout component that you can use in your web parts. The Office UI Fabric GitHub repo has a component called TileList which gives you a grid layout, but it is still in the experiments package which contains "not production-ready components and should never be used in [production code]". I'm not sure if Microsoft ever plans on moving the TileList component to the main Office UI Fabric component, but if they do, I'll update this article and code sample accordingly.

Instead, I created a GridList component that uses the Office UI Fabric List component and made a few modifications to make it look like a grid layout. I used the code from the List of 5000 grid items sample on the UI Fabric web site as the base for my code.

You can find the code in the src/components/GridList folder from my repository.

The control reacts the same way as the out-of-the-box grid layout. It even handles compact layouts:

Custom GridList control in action

Using the GridList component in your web part

Here's how to call the GridList component in your web part:

  1. If you haven't done so already, copy the content of the src/components/GridList folder from the sample code to your own project's src/components/GridList.
  2. In your src/webparts/[YourWebPartName]/components/[[YourWebPartName].tsx, retrieve the items you wish to display. To simplify this sample, I used a hard-coded list of items. I am too lazy (and unimaginative) to come up with my own sample data, so I used Mockaroo.com to generate a JSON structure that contains the items I wanted. I saved the items to my component's state in the constructor, as follows:
export default class GridLayout extends React.Component<IGridLayoutProps, IGridLayoutState> {
  constructor(props: IGridLayoutProps) {
    super(props);

    this.state = {
      items: [{
  thumbnail: "https://pixabay.com/get/57e9dd474952a414f1dc8460825668204022dfe05555754d742e7bd6/hot-air-balloons-1984308_640.jpg",
  title: "Adventures in SPFx",
  name: "Perry Losselyong",
  profileImageSrc: "https://robohash.org/blanditiisadlabore.png?size=50x50&set=set1",
  location: "SharePoint",
  activity: "3/13/2019"
}, {
  thumbnail: "https://pixabay.com/get/55e8d5474a52ad14f1dc8460825668204022dfe05555754d742d79d0/autumn-3804001_640.jpg",
  title: "The Wild, Untold Story of SharePoint!",
  name: "Ebonee Gallyhaock",
  profileImageSrc: "https://robohash.org/delectusetcorporis.bmp?size=50x50&set=set1",
  location: "SharePoint",
  activity: "6/29/2019"
}, {
  thumbnail: "https://pixabay.com/get/57e8dd454c50ac14f1dc8460825668204022dfe05555754d742c72d7/log-cabin-1886620_640.jpg",
  title: "Low Code Solutions: PowerApps",
  name: "Seward Keith",
  profileImageSrc: "https://robohash.org/asperioresautquasi.jpg?size=50x50&set=set1",
  location: "PowerApps",
  activity: "12/31/2018"
}, {
  thumbnail: "https://pixabay.com/get/55e3d445495aa514f1dc8460825668204022dfe05555754d742b7dd5/portrait-3316389_640.jpg",
  title: "Not Your Grandpa's SharePoint",
  name: "Sharona Selkirk",
  profileImageSrc: "https://robohash.org/velnammolestiae.png?size=50x50&set=set1",
  location: "SharePoint",
  activity: "11/20/2018"
}, {
  thumbnail: "https://pixabay.com/get/57e6dd474352ae14f1dc8460825668204022dfe05555754d742a7ed1/faucet-1684902_640.jpg",
  title: "Get with the Flow",
  name: "Boyce Batstone",
  profileImageSrc: "https://robohash.org/nulladistinctiomollitia.jpg?size=50x50&set=set1",
  location: "Flow",
  activity: "5/26/2019"
}]
    };
  }
  1. Define your component's props and state interfaces so that you have what you need to retrieve and store the items you wish to display (my props in this sample does not require anything so I left it empty):
export interface IGridLayoutProps {
// Add your own props here
}

export interface IGridLayoutState {
  items: IGridItem[];
}
  1. Define an interface to hold the properties for the items you wish to display. For my sample, I used the following interface:
export interface IGridItem {
  thumbnail: string;
  title: string;
  name: string;
  profileImageSrc: string;
  location: string;
  activity: string;
}
  1. In your src/webparts/[YourWebPartName]/components/[[YourWebPartName].tsx, onRender method, insert a GridList element, as follows:
public render(): React.ReactElement<IGridLayoutProps> {
    return (
      <div className={styles.gridLayout}>
        <GridList
          items={this.state.items}
          onRenderGridItem={(item: any, finalSize: ISize, isCompact: boolean) => this.onRenderGridItem(item, finalSize, isCompact)}
        />
      </div>
    );
  }
  1. Make sure to add an import for the GridList at the top of your file:
import { GridList } from '../../../components/gridList';

Note that your GridList element uses a method called onRenderGridItem which isn't defined yet. We'll do this next.

Using the DocumentCard component to render grid items

Typically, grids use cards to showcase content, but you can also use any rectangular content you wish to use.

In our sample code, we'll use Fabric UI DocumentCard components to render our content.

Our DocumentCard elements will contain two sub-elements:

  • DocumentCardPreview: which will contain a preview image of the item
  • DocumentCardDetails: which will contain further details

DocumentCard elements

The DocumentCardDetails itself contains more elements:

  • DocumentCardTitle: The title of the element
  • DocumentCardLocation: The "location" of the element. Often used to indicate an item's category or sub-title.
  • DocumentCardActivity: Used to indicate the item's latest activities, such as date last modified and last modified by

DocumentCardDetails elements

The DocumentCardActivity defines the following props:

  • activity: Describes the activity that has taken place, such as "Created Feb 23, 2020".
  • people: One or more people who are involved in this activity. Each person consists of the following two props:
    • name: The person's name you wish to display
    • profileImageSrc: The URL for the person's profile picture.

You can define more properties, but that's what I used for this sample. If you want to see the full list of properties, look at the IDocumentCardActivityPerson interface, and the IDocumentCardActivityProps interface on the Fabric UI documentation.

When the GridList wants to render a grid item, it will call your onRenderGridItem handler with three parameters:

  • item: The item it wants to render
  • finalSize: Size of the item to render
  • isCompact: Returns true if the grid is rendering in a compact mode.

If you return a DocumentCard element within every grid item, you'll do something like this:

private onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => {
    const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.cover,
          height: 130
        }
      ]
    };

    return <div
      className={styles.documentTile}
      data-is-focusable={true}
      role="listitem"
      aria-label={item.title}
    >
      <DocumentCard
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)} >
        <DocumentCardPreview {...previewProps} />
        <DocumentCardLocation location={item.location} />
        <DocumentCardDetails>
          <DocumentCardTitle
            title={item.title}
            shouldTruncate={true}
          />
          <DocumentCardActivity
            activity={item.activity}
            people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
          />
        </DocumentCardDetails>
      </DocumentCard>
    </div>;
  }

Make sure to add the following imports otherwise, your code will be sad:

// Used to render document cards
import {
  DocumentCard,
  DocumentCardActivity,
  DocumentCardPreview,
  DocumentCardDetails,
  DocumentCardTitle,
  IDocumentCardPreviewProps,
  DocumentCardLocation,
  DocumentCardType
} from 'office-ui-fabric-react/lib/DocumentCard';
import { ImageFit } from 'office-ui-fabric-react/lib/Image';

Note that in my code, I simply display an alert when someone clicks on a grid item. In your real code, you'll want to replace onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)} with your own onClick handler.

Notice that, in our previous code, we don't really use the isCompact parameter. It is passed to tell us whether we should render the grid using a compact layout or not.

Luckily, the DocumentCard component has a built-in compact layout that you can use by simply passing type={DocumentCardType.compact} if the DocumentCard should be compact. The default type for the DocumentCard is DocumentCardType.normal.

To handle the compact rendering, I'll simply change the DocumentCard's type depending on whether we're rendering in compact mode or not:

   <DocumentCard
        type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)}

      >

Finally, the out-of-the-box grid layouts tend to remove unnecessary information when rendering in compact mode (otherwise, there would just be too much stuff). The DocumentCardLocation is often omitted in compact mode. To mimic this behaviour, I only render the DocumentCardLocation if the layout isn't compact, using the following code:

{!isCompact && <DocumentCardLocation location={item.location} />}

Which makes the final code for your onRenderGridItem as follows:

private onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => {
    const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.cover,
          height: 130
        }
      ]
    };

    return <div
      className={styles.documentTile}
      data-is-focusable={true}
      role="listitem"
      aria-label={item.title}
    >
      <DocumentCard
        type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
        onClick={(ev: React.SyntheticEvent<HTMLElement>) => alert(ev)}

      >
        <DocumentCardPreview {...previewProps} />
        {!isCompact && <DocumentCardLocation location={item.location} />}
        <DocumentCardDetails>
          <DocumentCardTitle
            title={item.title}
            shouldTruncate={true}
          />
          <DocumentCardActivity
            activity={item.activity}
            people={[{ name: item.name, profileImageSrc: item.profileImageSrc }]}
          />
        </DocumentCardDetails>
      </DocumentCard>
    </div>;
  }

If all goes well, you get something like this:
Custom GridList control

Putting the finishing touches

If you replace the sample data from above with real data, your web part should be barely distinguishable from the out-of-the-box web parts.

For example: in the screen shot below, the top web part is the out-of-the-box Highlighted content web part, while the bottom one is my own, using (almost) the exact same code as show in this post.

Bottom part is custom

The only differences are:

  • My web part needs a title (that's easy to fix, just follow the instructions in my previous post)
  • In my onRenderGridItem, I added an iconSrc parameter to my previewProps to point to the document's icon, as follows:
const previewProps: IDocumentCardPreviewProps = {
      previewImages: [
        {
          previewImageSrc: item.thumbnail,
          imageFit: ImageFit.cover,
          height: 130,
          iconSrc: item.iconSrc
        }
      ]
    };

And, of course, I used an API call to retrieve the latest documents instead of returning hard-coded data.

Conclusion

There are many standardized web part layouts you can chose from. We discussed how to use the Grid layout in today's post. The code for today's post can be found in my GitHub repo.

In our next posts, we'll continue to discuss the standard web part layouts.

I hope this helps?

Update

  • July 29, 2019: Thanks everyone for the kind comments. I have submitted my grid layout control to the @pnp/spfx-controls-react library. Hopefully, the will find it as useful as you did!

Photo credits

Introduction

I love the SharePoint Design web site. It is a beautiful web site that provides design guidance on how to create beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

However, the site does not provide you with enough code samples to tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site. It provides you with code samples and detailed how-to information for every design topic.

It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post is the third on property panes. In part I, we covered the various types of property panes. In part II we showed how to create non-reactive web parts and how to add a loading indicator.

Today, we'll look at out-of-the-box web parts and we'll discuss how to replicate some of their property panes in your own web parts.

If you want to download the code samples used in this article, download the solution from the GitHub repo.

Create a choice group with images

One of the most frequently asked questions I see with property panes is about the property pane choice groups with images.

The out-of-the-box Highlighted content, Hero, Image gallery are examples of how the choice group property field is used.

Highlighted content choice groupo
Highlighted content web part with a choice group

Hero choice group
Hero web part choice group

You should use the property pane choice group when you have a small number of choices -- I recommend less than 7, no more than 9 choices -- where the user may not be immediately able to understand the differences between the choices.

For example, the difference between the Brick, Grid and Carousel layout in the Image gallery web part may not be immediately obvious to everyone. Adding images to represent each choice makes it easier to understand:

Image gallery choice group
Image gallery web part choice group

As it turns out, it's not very complicated to do.

Let's reproduce the Image gallery layout setting for our demo. To do so, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, create a property to store the new setting you want. We'll use layout. To make things easier to read, I'll just use possible values of Brick, Grid, or Carousel, but feel free to use an integer value, an enum, or any other data type you want:
    export interface IChoiceGroupWebPartProps {
    layout: 'Brick'|'Grid'|'Carousel';
    }
  2. At the top of the file, there should already an import statement for the @microsoft/sp-property-pane. To it, add an import for PropertyPaneChoiceGroup, which is the type of field we'll need to use:
    import {
    IPropertyPaneConfiguration,
    PropertyPaneChoiceGroup
    } from '@microsoft/sp-property-pane';
  3. You'll need an image or an icon for every choice you want to show in your choice group. I like to use SVG files with dimensions of 32px by 32px. I also like to store my web part images in a folder called assets in the [YourWebPartName] folder. For this sample, we'll use brick.svg, grid.svg and carousel.svg. Feel free to store the images wherever you like.
    The images within the web part
  4. The easiest way to include the image in your bundle is to use a require statement with the path to the file. Because I'll need to use each image twice (for each choice's imageSrc and selectedImageSrc), we'll define a variable for each image we'll need at the top of the getPropertyPaneConfiguration:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const layoutBrick: string = require('./assets/brick.svg');
    const layoutGrid: string = require('./assets/grid.svg');
    const layoutCarousel: string = require('./assets/carousel.svg');
    ...
  5. Add a propertyPaneChoiceGroup if your property pane's groupFields, making sure to pass the same image for the imageSrc and selectedImageSrc. Of course, you can have a different image for the selected and un-selected images if you'd like, but I don't usually. You getPropertyPaneConfiguration method will look as follows:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const layoutBrick: string = require('./assets/brick.svg');
    const layoutGrid: string = require('./assets/grid.svg');
    const layoutCarousel: string = require('./assets/carousel.svg');
    return {
      pages: [
        {
          header: {
            description: null
          },
          groups: [
            {
              //groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneChoiceGroup('layout', {
                  label: "Layout", // don't forget to localize your test in a real-world solution
                  options: [
                    {
                      key: 'Brick',
                      text: 'Brick',
                      selectedImageSrc: layoutBrick,
                      imageSrc: layoutBrick,
                    },
                    {
                      key: 'Grid',
                      text: 'Grid',
                      selectedImageSrc: layoutGrid,
                      imageSrc: layoutGrid,
                    },
                    {
                      key: 'Carousel',
                      text: 'Carousel',
                      selectedImageSrc: layoutCarousel,
                      imageSrc: layoutCarousel,
                    }
                  ]
                }),
              ]
            }
          ]
        }
      ]
    };
    }

    Note that in the above code, I did not localize the text to make the code easier to read. Please consider localizing all your text.

  6. I recommend that you define a default layout value in your [YourWebPartName]WebPart.manifest.json's preconfiguredEntries:
    "properties": {
      "layout": "Brick"
    }

    Remember that your changes to the manifest.json file will not take effect until you re-build your web part and re-add it to your workbench.

If you run gulp serve on your web part, you should see the following property pane:
Choice Group property pane

Create a choice group with Fabric icons

If you're lucky enough to find Office UI Fabric icons that suit your needs, it is even easier to create a choice group with icons.

To do so, follow these steps:

  1. Add a property to store your new setting. We'll use shape and we'll allow Circle, Square, or Triangle:
    export interface IChoiceGroupWebPartProps {
    layout: 'Brick'|'Grid'|'Carousel';
    // ADDED: For icon choice group
    shape: 'Circle'|'Square'|'Triangle';
    // END: added
    }
  2. Add a PropertyPaneChoicegroup control in your getPropertyPaneConfiguration method, making sure to pass a iconProps value instead of an imageSrc and selectedImageSrc. The iconProps should contain a single property officeFabricIconFontName, which should be set to the name of the icon you wish to use:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const layoutBrick: string = require('./assets/brick.svg');
    const layoutGrid: string = require('./assets/grid.svg');
    const layoutCarousel: string = require('./assets/carousel.svg');
    return {
      pages: [
        {
          header: {
            description: null
          },
          groups: [
            {
              //groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneChoiceGroup('layout', {
                  label: "Layout", // don't forget to localize your test in a real-world solution
                  options: [
                    {
                      key: 'Brick',
                      text: 'Brick',
                      selectedImageSrc: layoutBrick,
                      imageSrc: layoutBrick,
                    },
                    {
                      key: 'Grid',
                      text: 'Grid',
                      selectedImageSrc: layoutGrid,
                      imageSrc: layoutGrid,
                    },
                    {
                      key: 'Carousel',
                      text: 'Carousel',
                      selectedImageSrc: layoutCarousel,
                      imageSrc: layoutCarousel,
                    }
                  ]
                }),
                // ADDED: For icon-based choice group
                PropertyPaneChoiceGroup('shape', {
                  label: "Shape", // don't forget to localize your test in a real-world solution
                  options: [
                    {
                      key: 'Circle',
                      text: 'Circle',
                      iconProps: {
                        officeFabricIconFontName: 'CircleShapeSolid'
                      }
                    },
                    {
                      key: 'Square',
                      text: 'Square',
                      iconProps: {
                        officeFabricIconFontName: 'SquareShapeSolid'
                      }
                    },
                    {
                      key: 'Triangle',
                      text: 'Triangle',
                      iconProps: {
                        officeFabricIconFontName: 'TriangleShapeSolid'
                      }
                    }
                  ]
                })
                // END: Added
              ]
            }
          ]
        }
      ]
    };
    }
  3. As before, I recommend that you define a default shape value in your [YourWebPartName]WebPart.manifest.json's preconfiguredEntries:
    "properties": {
      "shape": "Square"
    }

Running gulp serve will give you the following:
Property pane with layout and shape choice groups

Conditional field in property panes

Sometimes, you want to show settings that are dependent on each other. If a user selects one choice, you may want to show a dependent setting, but if they select another choice, you may want to hide that setting.

The out-of-the-box Yammer web part does this by hiding or showing the Number of conversations to show setting depending on the conversation source the user selects.

Yammer web part shows and hides fields based on selection

This is what I'll call conditional fields for the purpose of this conversation.

Making fields conditional can help improve the user experience by removing choices that do not apply, therefore reducing the complexity of choice (remember Hick's law).

However, there are some guidelines you should follow:

  • Make sure that the user easily understands the reason why a field is shown or hidden. For example, the Yammer web part lets you see plainly that the Number of conversations is impacted by the conversation source.
  • Make sure that fields that depend on each other are logically grouped/visible together. For example, do not place one field on a property pane page (or step) and the field that depends on it on another page.
  • If you find it difficult to convey the relationship between two fields, you may wish to enable and disable the dependent field instead of hiding it. For bonus points, you should add some text (or, at least, some form of tooltip) that explains why the field is disabled.
  • If you want some options to be available some times, and not available some other times, consider disabling the option instead of hiding it. If the option will never be available because of a specific condition (e.g.: user doesn't have sufficient permissions, the wrong source selected, etc.), consider hiding the field instead.

To demonstrate this concept, we'll recreate the Select conversation source, Search for a sourcem and Number of conversations to show fields from the Yammer web part. To do so, follow these step steps:

  1. In your [YourWebPartName]WebPart.ts, add properties to store the conversation source, the search criteria, and the number of conversations:
    export interface IConditionalFieldWebPartProps {
    conversationSource: 'Group'|'User'|'Topic'|'Home';
    searchCriteria: string;
    numberOfConversations: number;
    }
  2. In the getPropertyPaneConfiguration, add the following code to render the property pane fields. Again, code is not localized to make it easier to read, please localize text in a real-world scenario:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "Select conversation source from groups, topics, users, or home."
          },
          groups: [
            {
              groupFields: [
                PropertyPaneDropdown('conversationSource',{
                  label: "Select conversation source",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: "Group",
                      text: "Group"
                    },
                    {
                      key: "User",
                      text: "User"
                    },
                    {
                      key: "Topic",
                      text: "Topic"
                    },
                    {
                      key: "Home",
                      text: "Home"
                    },
                  ]
                }),
                PropertyPaneTextField('searchCriteria', {
                  label: "Search for a source",
                  placeholder: "Type to search"
                }),
                PropertyPaneDropdown('numberOfConversations',{
                  label: "Number of conversations to show",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: 4,
                      text: "Small - 4 conversations"
                    },
                    {
                      key: 8,
                      text: "Medium - 8 conversations"
                    },
                    {
                      key: 12,
                      text: "Large - 12 conversations"
                    }
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
    }
  3. To Make the Search for a source field hide if the conversation source field is set to Home, add this.properties.conversationSource !== "Home" && in front of the line that begins with PropertyPaneDropdown('numberOfConversations'. Doing so says "Only execute the next line if the conversation source is not equal to Home". The new getPropertyPaneConfiguration method should look like this:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "Select conversation source from groups, topics, users, or home."
          },
          groups: [
            {
              groupFields: [
                PropertyPaneDropdown('conversationSource',{
                  label: "Select conversation source",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: "Group",
                      text: "Group"
                    },
                    {
                      key: "User",
                      text: "User"
                    },
                    {
                      key: "Topic",
                      text: "Topic"
                    },
                    {
                      key: "Home",
                      text: "Home"
                    },
    
                  ]
                }),
                // ADDED: conditional rendering for field
                this.properties.conversationSource !== "Home" && PropertyPaneTextField('searchCriteria', {
                  label: "Search for a source",
                  placeholder: "Type to search"
                }),
                PropertyPaneDropdown('numberOfConversations',{
                  label: "Number of conversations to show",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: 4,
                      text: "Small - 4 conversations"
                    },
                    {
                      key: 8,
                      text: "Medium - 8 conversations"
                    },
                    {
                      key: 12,
                      text: "Large - 12 conversations"
                    }
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
    }
  4. No necessary for this example, but if you want the mimic the Yammer web part and enable/disable the Number of conversations field on the selection, add a isDisabled attribute to the PropertyPaneDropDown field, as follows:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "Select conversation source from groups, topics, users, or home."
          },
          groups: [
            {
              groupFields: [
                PropertyPaneDropdown('conversationSource',{
                  label: "Select conversation source",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: "Group",
                      text: "Group"
                    },
                    {
                      key: "User",
                      text: "User"
                    },
                    {
                      key: "Topic",
                      text: "Topic"
                    },
                    {
                      key: "Home",
                      text: "Home"
                    },
    
                  ]
                }),
                this.properties.conversationSource !== "Home" && PropertyPaneTextField('searchCriteria', {
                  label: "Search for a source",
                  placeholder: "Type to search"
                }),
                PropertyPaneDropdown('numberOfConversations',{
                //ADDED: To enable/disable field based on selection
                  disabled: this.properties.conversationSource !== "Home",
                  label: "Number of conversations to show",
                  selectedKey: this.properties.conversationSource,
                  options: [
                    {
                      key: 4,
                      text: "Small - 4 conversations"
                    },
                    {
                      key: 8,
                      text: "Medium - 8 conversations"
                    },
                    {
                      key: 12,
                      text: "Large - 12 conversations"
                    }
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
    }
  5. Set default property values in your [YourWebPartName]WebPart.manifest.json, under preconfiguredEntries:
    "properties": {
      "conversationSource": "Group",
      "searchCriteria": "",
      "numberOfConversations": 8
    }

Running a new gulp serve should give you a web part pane that behaves like the Yammer web part:

The logic for enabling/disabling and hiding/showing property pane fields in Yammer is more complicated than what we cover in this example, but I hope it'll do for now.

In my opinion, the Yammer Number of conversations should probably tell you why it is disabled. If it is never supposed to be available from any other sources than Home, it should probably be hidden instead of disabled -- but that's my personal preference, not a hard rule.

Note: Don't worry, we'll cover how to create a drop-down box with icons in a later post.

Creating conditional property pane groups

The out-of-the-box Quick chart web part allows users to choose between a Column chart or a Pie chart. If the user selects a Column chart, the property pane will show a Data property group and a Layout property group; If they choose Pie chart, the property pane will hide the Layout property group and only show the Data property group.

Quick chart with conditional property groups

By hiding or showing only the property pane groups that are relevant to the user's selected chart type, the Quick chart web part helps the user quickly configure the web part without presenting unnecessary settings.

Imagine if -- instead of hiding the Layout property pane group -- we disabled the property group entirely. The screen would be needlessly cluttered.

A while ago, I created a Chartinator web part sample for the SharePoint sp-dev-fx-webparts samples repository, which extends on the Quick chart web part and allows users to configure 9 different types of charts. Depending on which chart type the user selects, multiple property pane groups appear and disappear to help users make sense of the options available to them. If we didn't hide some sections, the property pane would be much too complicated to use.

The Chartinator web part

For this code sample, we'll emulate the Quick chart property pane and allow users to select one of two types of charts. Depending on the selection, we'll hide or show the Layout property pane group. The Data property pane group will always be available.

To keep this sample simple, we won't add any property fields in either property pane group -- we'll just put some text to illustrate the concept. If you really want to see how to implement the full Quick chart property pane, check out the Chartinator sample.

To mimic the Quick chart web part property pane, follow these steps:

  1. As before, add a property to your web part to store the Chart type selection:
    export interface IConditionalGroupWebPartProps {
    chartType: "Column" | "Pie";
    }
  2. Change the getPropertyPaneConfiguration method to create an IPropertyPaneConfiguration variable to store the property pane configuration (instead of simply returning it) so that we can manipulate the content of the property pane configuration.

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const chartDescription: string = this.properties.chartType === "Column" ?
      "Use a column chart to show data changes over time or comparisons among items. Categories are typically shown along the horizontal axis and values along the vertical axis."
      : "Use a pie chart to show percentages of a whole. Best when used with fewer than seven categories.";
    
      const configuration: IPropertyPaneConfiguration = {
      pages: [
        {
          header: {
            description: "Select a chart type and then select a data source. You can enter up to 12 data points, or show up to 50 data points if you use a SharePoint list on this site as the data source."
          },
          displayGroupsAsAccordion: true,
          groups: [
            {
              groupName: "Chart type",
              groupFields: [
                PropertyPaneChoiceGroup('chartType', {
                  options: [
                    {
                      key: 'Column',
                      text: 'Column chart',
                      iconProps: {
                        officeFabricIconFontName: 'BarChart4'
                      }
                    },
                    {
                      key: 'Pie',
                      text: 'Pie chart',
                      iconProps: {
                        officeFabricIconFontName: 'PieDouble'
                      }
                    }
                  ]
                }),
                PropertyPaneLabel('chartType', {
                  text: chartDescription
                }),
    
              ]
            },
            {
              groupName: "Data",
              isCollapsed: false,
              groupFields: [
                PropertyPaneLabel('data', {
                  text: "This is some sample text for the data property group."
                }),
              ]
            }
          ]
        }
      ]
    };
    
    return configuration;
    }
  3. At the end of the same method, add some conditional logic to insert a new element in the groups array using push:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    const chartDescription: string = this.properties.chartType === "Column" ?
      "Use a column chart to show data changes over time or comparisons among items. Categories are typically shown along the horizontal axis and values along the vertical axis."
      : "Use a pie chart to show percentages of a whole. Best when used with fewer than seven categories.";
    
      const configuration: IPropertyPaneConfiguration = {
      pages: [
        {
          header: {
            description: "Select a chart type and then select a data source. You can enter up to 12 data points, or show up to 50 data points if you use a SharePoint list on this site as the data source."
          },
          displayGroupsAsAccordion: true,
          groups: [
            {
              groupName: "Chart type",
              groupFields: [
                PropertyPaneChoiceGroup('chartType', {
                  options: [
                    {
                      key: 'Column',
                      text: 'Column chart',
                      iconProps: {
                        officeFabricIconFontName: 'BarChart4'
                      }
                    },
                    {
                      key: 'Pie',
                      text: 'Pie chart',
                      iconProps: {
                        officeFabricIconFontName: 'PieDouble'
                      }
                    }
                  ]
                }),
                PropertyPaneLabel('chartType', {
                  text: chartDescription
                }),
    
              ]
            },
            {
              groupName: "Data",
              isCollapsed: false,
              groupFields: [
                PropertyPaneLabel('data', {
                  text: "This is some sample text for the data property group."
                }),
              ]
            }
          ]
        }
      ]
    };
    // ADDED: To insert a conditional group
    // If the selected type is not Column, we don't need to make any further changes
    if (this.properties.chartType !== "Column") {
      return configuration;
    }
    
    // Get the list of property groups
    const { groups } = configuration.pages[0];
    
    // Insert a property pane
    groups.push({
      groupName: "Layout",
      isCollapsed: false,
      groupFields: [
        PropertyPaneLabel('layout', {
          text: "This is some sample text for the layout property group."
        }),
      ]
    });
    
    // END: added
    return configuration;
    }
  4. Make sure to define some default properties in your web part's manifest.json file:
    "properties": {
      "chartType": "Column"
    }

On your next gulp serve, you'll have a web part property pane hides/shows conditional property groups just like the Quick chart web part:

Sample quick chart web part with conditional groups

At the risk of repeating myself: the sample does not localize the text to help keep the code easy to read. It is killing me to do this. Please localize your text in a real solution.

Conclusion

In today's post, we discussed how to create image choice groups, create conditional property pane fields and conditional property pane groups.

All the code samples for this post can be found in my sample solution from the GitHub repo.

Every time I created a web part for this series so far, I wanted to change the body of the web part to make it look better (instead of using the default styles rendered by the Yeoman generator). It took everything in me not to tinker with the web part body and create something that would be worthy of the SharePoint Design series.

In our next post, we'll focus on the web part body.

Finally!!!

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post continues on my previous post. It discusses various aspects of property panes that you may find useful.

Like the previous post, this is a companion to the Designing SharePoint web part page.

If you want to download the code samples used in this article, download the solution from the GitHub repo.

Reactive and non-reactive web parts

By default, when you change settings in a property pane, the changes are immediately reflected within the web part on the page; the web part is considered reactive.

Reactive web parts are recommended because it follows the WYSIWYG (what you see if what you get) user experience principles for authoring.

However, sometimes you may not want every change a user makes in the property pane to immediately be reflected in the web part.

For example, if your web part has to load data or call an API every when your user changes settings, you may want to make your web part non-reactive.

When you make your web part non-reactive, your property pane will add an Apply button at the bottom of your property pane. Any changes users make in your property pane will not take effect until they select Apply.

To make your web part non-reactive, all you need to do define a disableReactivePropertyChanges function in your web part, and return true. To do so, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, find the getPropertyPaneConfiguration function.
  2. Add the following code before or after (it's really up to you):
    protected get disableReactivePropertyChanges(): boolean {
    return true;
    }

That's it!

When you run your web part, the property pane will display the Apply button we discussed.

Non-reactive property pane

Showing a property pane loading indicator

You may have noticed that most web part property panes do not show a loading indicator. That's because we always want web parts to appear fast and responsive.

A loading indicator
This is what we mean by 'Loading indicator'

However, sometimes you need to make asynchronous requests to load some data for your property pane, which could take a long time. That's when you should display a loading indicator to the end-user, so they don't think your web part has crashed or has become unresponsive.

How long before a user thinks the system has crashed? According to the Doherty Threshold law of user experience, you should provide feedback within 400 milliseconds in order to keep your user's attention and increase their trust in the system.

If your async requests take longer than 400 milliseconds, you should display a loading indicator.

When I want to display a loading indicator in the property pane, I follow these steps:

  1. First, you'll need a variable to store whether you should display the loading indicator or not (otherwise, your loading indicator will always display!). To do so, add a boolean variable to you [YourWebPartName]WebPart class:

    export default class LoadingIndicatorWebPart extends BaseClientSideWebPart<ILoadingIndicatorWebPartProps> {
    
    // ADDED: To store whether we should display the loading indicator or not
    private showLoadingIndicator: boolean = true;
    // END: added
    ...

    In my code, I always initially set it to true because I usually only load my asynchronous data once -- the first time a user displays the property pane.

  2. Add an onPropertyPaneConfigurationStart method to your web part class if you don't already have one. It will get called when preparing the property pane to display. This is where I like to make my asynchronous calls:
    // ADDED: To display the loading indicator when preparing the property pane
    protected async onPropertyPaneConfigurationStart(): Promise<void> {
    // Call your service
    // Remember that this method gets called *every time* before a user
    // displays the property pane. You should probably verify that your
    // data isn't already loaded before calling your async method again
    this.loadedPlantList = await this.getPlantNames();
    }
    // END: Added

    Note that in my code, I use an await statement to wait until my async call is complete. If you prefer, you could use this.getPlantNames().then(...), but await calls are quickly becoming the preferred way to handle async calls in React.

  3. Once your data is loaded, set your showLoadingIndicator variable to false (line 10):

    protected async onPropertyPaneConfigurationStart(): Promise<void> {
    // Call your service
    // Remember that this method gets called *every time* before a user
    // displays the property pane. You should probably verify that your
    // data isn't already loaded before calling your async method again
    this.loadedPlantList = await this.getPlantNames();
    
    //ADDED: To stop displaying the loading indicator
    // When done loading, set the loading indicator to false and refresh
    this.showLoadingIndicator = false;
    //END: Added
    }
  4. To make sure that your property pane updates when you turn the loading indicator on or off, make sure to call refresh on the property pane -- otherwise it won't take effect (line 12):

    protected async onPropertyPaneConfigurationStart(): Promise<void> {
    // Call your service
    // Remember that this method gets called *every time* before a user
    // displays the property pane. You should probably verify that your
    // data isn't already loaded before calling your async method again
    this.loadedPlantList = await this.getPlantNames();
    
    // When done loading, set the loading indicator to false and refresh
    this.showLoadingIndicator = false;
    
    //ADDED: To force the property pane to refresh
    this.context.propertyPane.refresh();
    //END: Addd
    }
  5. Finally, in your getPropertyPaneConfiguration, set the property pane's showLoadingIndicator prop to the value of your showLoadingIndicator variable (line 4):
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      // ADDED: To display a loading indicator
      showLoadingIndicator: this.showLoadingIndicator,
      // END: Added
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('description', {
                  label: strings.DescriptionFieldLabel,
                  options: this.loadedPlantList
                })
              ]
            }
          ]
        }
      ]
    };
    }

That's all you should need to do (in theory). In my code sample, I call a fake service to get a list of plant names and load it in a property pane drop-down box. This is what the final product looks like (note that I exaggerated the delay for demo purposes):

Property pane with loading indicator

Delaying the loading indicator

As we discussed, we want to avoid showing the loading indicator if we can. We especially want to flash (i.e.: briefly show) a loading indicator every time we load the property pane.

But you also want to keep in mind the Doherty Threshold. Delay a response for longer than 400 milliseconds and people will start clicking around.

Luckily, you can delay the loading indicator and do both!

Let's pretend that you make a call to a service which always returns with results within 300 milliseconds -- except when everyone at work is watching YouTube, then it can take a lot longer. You may want to avoid showing a loading indicator as long as your async calls take less than 300 milliseconds, but start display an indicator if the calls take longer.

All you need to do is add a loadingIndicatorDelayTime value to your property pane configuration. The system will wait for whatever delay time you specified before displaying the loading indicator. If you turn off the loading indicator before the delay has elapsed (i.e.: when your async call returns within normal conditions), the loading indicator will never show. However, if you don't turn off the indicator before the delay period, the loading indicator will automatically start showing.

To do so, change your code as follows:

  1. In your web part's getPropertyPaneConfiguration method, add the loadingIndicatorDelayTime (line 5):
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      showLoadingIndicator: this.showLoadingIndicator,
      // ADDED: To delay the loading indicator
      loadingIndicatorDelayTime: 300,
      // END: Added
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('description', {
                  label: strings.DescriptionFieldLabel,
                  options: this.loadedPlantList
                })
              ]
            }
          ]
        }
      ]
    };
    }

Two things to note:

  • If you don't specify a loadingIndicatorDelayTime value, and your showLoadingIndicator is set to true, SharePoint will wait 500 milliseconds by default.
  • The loadingIndicatorDelayTime also seems to delay displaying the property pane until either the delay has elapsed or your showLoadingIndicator is set to false.

Conclusion

Today we expanded a little on how to create professional property panes by making them reactive (or not) and by adding a loading indicator when necessary.

There is a lot more to cover on property panes, but we'll covert it in our next post.

I hope this helps?

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post is a companion to the Designing SharePoint web part page.

What is a property pane?

The property pane is a panel that appears when users select Edit on a web part. It allows users to enable and disable features, select a layout, connect to another web part, or set other options.

Web Part with a property pane highlighted
The property pane

When visible, the property pane is 320 pixels, which makes it the perfect size to appear full-screen on a mobile device. On a tablet or desktop device, the page content area shrinks by 320 pixels and the content re-flows responsively to make room for the property pane.

Property pane in edit mode
The property pane in action

When to use a property pane

You should use a property pane when you need to offer options to your users to configure the web part.

You should not use a property pane to edit the web part's content.

Directness is a user interface design principle which says that your users should be able to achieve their goals through a minimal set of actions. It also says that users should be able to manipulate the objects they're working on in the most direct manner.

In other words, directness says that, whenever possible, you should allow users to edit the content of a web part where the content actually resides -- in the web part's content area.

That's why the out-of-the-box Text and Hyperlink web parts make you edit content directly within the web part.

Directness in text web part
Text web part in action

Directness in hyperlink web part
Hyperlink web part in action

In contrast, the Image web part opens a pane to insert and edit an image, because there isn't a natural way to create an image directly from the web part's content area.

Image web part uses a property pane
Image web part's use of the property pane

In some cases, you may wish to use the property pane to provide additional options for the selected content of the web part. For example, the Text web part uses the property pane to provide users with additional formatting options that would otherwise clutter the text editing toolbar. Users edit the content directly within the web part, but can also use additional formatting options in the property pane.

Text web part using the property pane to format text
The text web part uses the property pane to provide additional formatting options

The best way to leverage property panes it to use it to provide options to your page authors that you don't want other users (i.e.: readers) to see. Because SharePoint automatically hides or shows the Edit depending on the user's permissions, you don't have to build your own sophisticated security logic to hide or show options within your web part.

If in doubt about whether you should show an option within the web part body or within the property pane, ask yourself whether you want all users to be able to control that option. For example, if you want to allow all users to refresh a feed via a Refresh button, or if you want users to control whether they want their personal feed to be sorted in ascending or descending order, give the users the option to do that within the web part's body.

On the other hand, if you only want page authors to control options that will affect all users, you should probably place that option within the property pane.

Take a look at the various out-of-the-box web parts to see how they present their various options for inspiration.

Types of property panes

When designing a property pane, you can use one of three property pane types:

Single pane

Used for simple web parts with a few properties to configure. As the name implies, there is only one single pane without any grouping.

Single pane

Accordion pane

Contains a group or groups of properties with many options, and where the groups result in a long scrolling list of options.

For example, you might have three groups named Properties, Appearance, and Layout, each with many options.

Accordion pane

Steps pane

Group properties in multiple steps or pages. Used when web configuration needs to follow a precise order of steps, or when choices in the first step affect the options that display in next steps.

The property pane will display a Back and Next buttons at the bottom of the pane, along with an indication of how many steps there are, and which step is currently displayed.

Steps pane

For more information on each property pane type refer to the SharePoint Design site.

Create a single pane property pane

To create a single pane property pane, follow these steps:

  • Do nothing

That's because it is what the SPFx Yeoman generator automatically produces for you when you create a web part.

Let's take a second to explore the default code to understand it better.

When you create a web part using the Yeoman generator, this is the standard getPropertyPaneConfiguration method that you get in your [YourWebPartName]WebPart class:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('description', {
                  label: strings.DescriptionFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }

If you aren't familiar with SPFx, Typescript, or React, it may not be obvious what each part of the code does. The biggest reason is that the SharePoint team is being really nice to us and provides us with a template that encourages you to localize all your web part resources (i.e.: the text in your web parts). That's why you see strings.PropertyPaneDescription as the description for the property pane. It is a great habit to follow, and I encourage you to keep localizing all your resources -- even if your web part is only ever going to be in one language. It makes it easier to change the text (like when fixing spelling mistakes) without having to touch the code.

To make things easier, let's replace the code and see what happens. Replace your existing getPropertyPaneConfiguration code with the following code:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This is the pane description"
          },
          groups: [
            {
              groupName: "This is the group name",
              groupFields: [
                PropertyPaneTextField('description', {
                  label: "This is the field label"
                })
              ]
            }
          ]
        }
      ]
    };
  }

When test your web part (using gulp serve), your property pane will look like this:

Property pane describing each component

If we use our browser's developer toolbar to explore the various elements, we can see the individual components:

Highlights each section of a property pane

You may have noticed that the property pane's title isn't configured anywhere. That's because it uses the web part's title, which is configured in the [YourWebPartName].manifest.json under preconfiguredEntries > title > default. If you change the title, remember that the settings won't take effect until you re-bundle the web part (by running gulp serve again, or gulp build) and re-add the web part to your page.

Property pane page description

I encourage you to use the page description to provide your users with instructions on what you expect them to do. For example, this is what the out-of-the-box Image property pane page description looks like:

Change your image and image options. Turn on or off the display of text over your image, add a link, and add or modify alternative text.

The File viewer web part gives you different directions based on what type of file you select. This is what it says if you select a PowerPoint presentation:

Select the slide you want people to see first.

And this is what you get if you selected a Word document:

Select the page you want people to see first.

If you didn't select a file, however, this is what you'll see:

Add a file to view on your page. You can select from a variety of file types including Excel, Word, PowerPoint, Visio, PDFs, 3D models, and more. You can also connect to a source to dynamically view files by selecting the ellipses (…) and Connect to source.

Last example: this is what the Highlighted content web part page description says:

Select the content you want to highlight, and choose layout options.

Use your property pane page description to provide users with useful guidance. If you don't have anything useful to say, you can simply omit the page description by removing the line in your code:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          // REMOVED: to remove the description
          // header: {
          //   description: "This is the pane description" //strings.PropertyPaneDescription
          // },
          groups: [
            {
              groupName: "This is the group name", //strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('description', {
                  label: "This is the field label" //strings.DescriptionFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }

Which gets you this:

Property Pane without Description
Property pane without a description

For the record, though, I encourage you to provide some helpful text instead of removing the description.

Property pane group

Your property pane can have more that one group. However, when you use the single pane property pane when you have few options to present, otherwise you should use the accordion or steps property pane.

You may have noticed that some of the out-of-the-box web parts have no property pane groups. Or, rather, they have a single property pane group with no group header.

Image web part property pane
The image web part property pane has no group header

Text web part property pane
The text web part property pane has no group header either

To reproduce the same look, all you need to do is omit a group header in your code:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          // REMOVED: To hide the property pane page description
          // header: {
          //   description: "This is the pane description"
          // },
          groups: [
            {
              //REMOVED: For single pane property pane
              //groupName: "This is the group name",
              groupFields: [
                PropertyPaneTextField('description', {
                  label: "This is the field label"
                })
              ]
            }
          ]
        }
      ]
    };
  }

Which produces the following web part property pane:

Single pane, no header, no group header

Create an accordion pane property pane

Sometimes you have too many options to present to your users at one time.

Take the out-of-the-box Highlighted content web part, for example. Imagine if showed all available options without any grouping. It would look like this:

Highlighted content web part with no grouping
Too many options at once!

Now take the same web part, but group the options logically. You get something like this:

Highlighted content web part with grouping
Ah! Much easier to understand!

It makes sense to group your choices into smaller, collapsible sections known as accordion panes.

Why should you do this? It's the law!

Hick’s Law, to be precise. Also known as the Hick-Hyman Law, it is named after a British and an American psychologist team of William Edmund Hick and Ray Hyman. In 1952, they found a relationship between the number of stimuli present and an individual's reaction time to any given stimulus.

Specifically, they found that the more stimuli to choose from, the longer it takes the user to make a decision.

(That's what I like to call the Cheesecake Factory Law, because of how long it takes to decide what to eat when at the Cheesecake Factory. Have you seen how long their menu is?!)

When you bombard your users with too many choices at once, they have to take time to interpret and decide, giving them work they don't want.

If you have a lot of choices to offer your users, consider grouping them into smaller choices. For example, instead of giving you 7 configuration choices to pick from, the Highlighted content web part gives you three simple choices:

  • Content
  • Filter and sort
  • Layout

Within each of those three, you have two or three smaller choices to make. Making it easier for users to make sense of the choices that are available to them.

Note that the rule only applies to choices that the user is unfamiliar with. For example, if you want to list the months of the year, don't group them into smaller choices. Otherwise, you'll actually increase the time it takes for a user to find the item they want.

For this sample, we have created 6 properties (demoProperty1 through demoProperty6) which we'll group into three categories (Group 1, Group 2 and Group 3). Feel free to use your own categories and properties.

If you want to create an accordion pane property pane, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, find the getPropertyPaneConfiguration function and create as many groups as you need, with fields within those groups.
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This web part demonstrates how to use an accordion property pane"
          },
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            // ADDED: Group 2 and 3 for accordion support
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            },
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            }
            // END added
          ]
        }
      ]
    };
    }

    The groups and groupFields nodes expect arrays of items, which means that we can have many groups and many fields within the groups.

  2. To make the groups appear as accordions, add a displayGroupsAsAccordion property and set it to true, as follows (see line 9):
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This web part demonstrates how to use an accordion property pane"
          },
          // ADDED: to turn groups into accordions
          displayGroupsAsAccordion: true,
          // END added
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            },
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            }
          ]
        }
      ]
    };
    }
  3. If you wish to make the groups initially appear as expanded or collapsed, you can specify the isCollapsed property at the group level and set it to true or false, as desired:
    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This web part demonstrates how to use an accordion property pane"
          },
          displayGroupsAsAccordion: true,
          groups: [
            {
              groupName: "Group 1",
              // ADDED: to collapse group initially
              isCollapsed: true,
              // END added
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              // ADDED: to collapse group initially
              isCollapsed: true,
              // END added
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            },
            {
              groupName: "Group 3",
              // ADDED: to collapse group initially
              isCollapsed: true,
              // END added
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            }
          ]
        }
      ]
    };
    }
  4. If you run gulp serve, you should get an accordion pane property pane:
    A custom accordion pane

Note: To keep the code easier to read, we did not localize our text resources. Please don't do this at home.

Create a steps pane property pane

As we previously discussed, if you have a lot of configuration options in your web part, you should use an accordion pane and group your configuration options to make it easier for your users. Accordion panes work well when you want to allow your users to configure your web part in no particular order.

Sometimes, you need your users to configure your web part by following a specific sequence.

That's when you should use steps pane property panes.

To prepare this sample, we created demoProperty1 through demoProperty10. We'll show these options across 3 pages, divided into 5 groups. Feel free to use your own properties.

Just like the groups and groupField property, the pages property expects an array. You can define more than one page and they will be displayed in the same order that you defined them in the code.

To create our sample steps pane property pane, follow these steps:

  1. In your [YourWebPartName]WebPart.ts file, find the getPropertyPaneConfiguration function and create as many pages as you need, with groups and group fields within those pages:

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: "This is the first page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            }
          ]
        },
        // ADDED: To add steps
        {
          header: {
            description: "This is the second page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            },
            {
              groupName: "Group 4",
              groupFields: [
                PropertyPaneTextField('demoProperty7', {
                  label: "Property 7"
                }),
                PropertyPaneTextField('demoProperty8', {
                  label: "Property 8"
                })
              ]
            }
          ]
        },
        {
          header: {
            description: "This is the third and final page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 5",
              groupFields: [
                PropertyPaneTextField('demoProperty9', {
                  label: "Property 9"
                }),
                PropertyPaneTextField('demoProperty10', {
                  label: "Property 10"
                })
              ]
            }
          ]
        }
        //END added
      ]
    };
    }
  2. By default, the web part will start the property pane on the first page. If you'd like to override this (for example, if you want to bring attention to a specific page to encourage a user to solve an issue), specify the currentPage property as follows (see line 4):

    protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      // ADDED: To change the default current page
      currentPage: 3,
      // END added
      pages: [
        {
          header: {
            description: "This is the first page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 1",
              groupFields: [
                PropertyPaneTextField('demoProperty1', {
                  label: "Property 1"
                }),
                PropertyPaneTextField('demoProperty2', {
                  label: "Property 2"
                })
              ]
            },
            {
              groupName: "Group 2",
              groupFields: [
                PropertyPaneTextField('demoProperty3', {
                  label: "Property 3"
                }),
                PropertyPaneTextField('demoProperty4', {
                  label: "Property 4"
                })
              ]
            }
          ]
        },
        // ADDED: To add steps
        {
          header: {
            description: "This is the second page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 3",
              groupFields: [
                PropertyPaneTextField('demoProperty5', {
                  label: "Property 5"
                }),
                PropertyPaneTextField('demoProperty6', {
                  label: "Property 6"
                })
              ]
            },
            {
              groupName: "Group 4",
              groupFields: [
                PropertyPaneTextField('demoProperty7', {
                  label: "Property 7"
                }),
                PropertyPaneTextField('demoProperty8', {
                  label: "Property 8"
                })
              ]
            }
          ]
        },
        {
          header: {
            description: "This is the third and final page."
          },
          displayGroupsAsAccordion: false,
          groups: [
            {
              groupName: "Group 5",
              groupFields: [
                PropertyPaneTextField('demoProperty9', {
                  label: "Property 9"
                }),
                PropertyPaneTextField('demoProperty10', {
                  label: "Property 10"
                })
              ]
            }
          ]
        }
        //END added
      ]
    };
    }

Try to avoid skipping directly to another page without a valid reason, otherwise, your users may be a bit confused. If you choose to skip to a page other than page 1, help the users understand why you did it -- perhaps with a little message at the top of the page.

Once completed, you should get a property pane with three pages (I kept the default page to page 1, in case you're wondering:
A steps pane property pane

Conclusion

That's probably enough for one day.

As you hopefully noticed, creating property panes isn't very difficult. Most of the work is already done for you.

The code for this post is available on the SharePoint Framework Web Part Design Series repo.

In our next post, we'll continue discussing property panes with some advanced options, such as adding images to property pane headers, showing loading indicators, and making responsive/non-responsive property pane web parts. We'll also discuss what the text within these property panes should look like.

Then we'll discuss more exciting stuff, like creating conditional property pane groups and fancy property pane fields.

I hope this helps?

Introduction

The SharePoint Design is a beautiful web site that provides design guidance on beautiful and fast sites, pages, and web parts with SharePoint in Office 365.

Unfortunately, the SharePoint Design site does not tell you how to create the beautiful web parts they show you.

This series is intended as a companion to the SharePoint Design site, providing you with code samples and detailed how-to information for every design topic. It should help you create web parts that look exactly like the ones on the SharePoint Design site.

Today's post is a companion to the Titles and descriptions for SharePoint web parts

What is a Web Part Title?

A Web Part title is a heading that appears above a web part to visually separate the web part from other content and to provide information to users about the purpose of a web part.

Source: Microsoft

Most often, web parts provide a default title that page authors can override (or remove) to customize the web parts to suit their needs.

What is a Web Part Description

The Web Part description is often smaller text within the web part that describes the web part content. For example, the caption element in the Image web part.

The caption in the image web part is an example of a web part description

Just like the title, page authors can override or omit the web part description to suit their needs.

It is important to not confuse description with alternative text. The description is optional, but it is always visible.

The alternative text is something that you should always provide for visual content (such as images, charts, etc.). We'll discuss alternative text in a later post.

Why use titles and descriptions?

Understanding

Back in my McKinsey & Company days, a wise person told me:

When you leave it up to your audience to make an assumption, you lose control over that assumption

Seems kind of obvious, but what they meant was that if you present information to users and expect them to make a conclusion from it, they may make the wrong conclusion.

Don't assume your users are stupid, but don't assume that by just putting a web part on the page that they will understand the purpose of the web part is.

Titles and descriptions are a consistent way to indicate to your users the purpose of your web part.

Accessibility

When I tell people about accessibility requirements in software design, most people's initial reaction is often something like:

"We don't have any blind people [who work here], so we don't need to worry about accessibility"

Unfortunately, accessibility is more than about blindness. It is estimated that more than 10% of the population live with a disability.

Accessibility is actually broken down into 4 categories:

  • Visual: This can include blind (or non-sighted) users, but also includes users with low-vision, users with obstructed vision, or age-related visual impairment. This includes color blindness, which affects 1 in 12 men (or about 8%) and 1 in 200 women.
  • Auditory: This includes hearing-impaired users.
  • Motor: People with motor impairments includes people with Repetitive Stress Injuries, Cerebral palsy, Parkinson’s and Muscular dystrophy.
  • Cognitive: which relates to the ease processing of information. It includes people with autism, Down's syndrome, Dyslexia, or global developmental delay. Consider stroke victims and concussion victims as well.

Accessibility may be a permanent or temporary condition. People often (wrongly) assume that web accessibility is only for people with permanent or long-term disabilities, but accessibility benefits people with or without disabilities. For example, it may impact the following people:

  • People who are not fluent in English.
  • People who do not have or are unable to use a keyboard or mouse.
  • People with temporary disabilities due to accident or illness.
  • Older people.
  • New users.

Plus, in some countries, you are now legally obligated to provide accessible resources to your employees.

Lucky for you, the use of titles and descriptions in web parts help alleviate some of the accessibility issues by providing headings, plenty of white space on the page, visually grouping information together, and even tackling colour contrast requirements on the page -- to name only a few benefits.

When should you use titles

Let's pretend for a second that we are not talking about SharePoint, but Microsoft Word instead. You can add things in a Word document that will make the body of the document. Things like text, images, hyperlinks, page breaks, etc. When you insert such elements in a Word document, you want them to blend in together and print nicely.

If your web part is intended to blend in with other content within your SharePoint page, you probably don't need a title. For example, the following out-of-the-box web parts do not need a title:

  • Text
  • Image
  • File viewer
  • Link
  • Embed
  • Divider
  • Markdown

On the other hand, if your web part provides a new set of information that should be visually distinctive from the rest of your page content, it should probably provide a title.

For example, if you add the Events web part or the Document library web part in a page, you probably don't want to dump a list of events or documents in between two paragraphs without providing context to your users.

This decision tree may help. You can tell that it was written by a consultant because one of the answers is "it depends".

st=>start: Does my web part need a title?
e=>end: No title needed
cond=>condition: Does your web part 
need to blend in?
cond2=>condition: Does it need to visually
group information?
io=>parallel: No title needed
para=>end: Title is recommended
st->cond
e3=>end: It depends :-)
cond(yes)->e
cond(no)->cond2(yes)->para
cond2(no)->e3

When should you use descriptions

Unlike the web part title, the description is not intended to visually distinguish the web part. It is purely intended to help understand the purpose or context of a web part. I recommend providing the option for a description in your web parts if the body of your web part mostly consists of images or graphics. For example, a chart web part, or an image.

In my Comparer web part, which allows users to compare two images side-by-side, I allow editors to provide a description for each image.

How to add a web part title to your web part

If you want to see the sample web part I'm building in this post, visit the GitHub repo and open the WebPartTitles solution. The web part is called EditableTitle

I chose the web part title as the first in this series because it is probably the easiest one to implement. That's because the PnP Reusable Controls library provides a perfect Web Part Title component.

Web Part Title Control in Action

Here is how to add the web part title in your SPFx web part solution using React:

  1. Using your Node.js Command Prompt (or whatever terminal you wish to use), make sure that you're in your web part solution's root directory. (It should be the same folder where your solution's package.json is located).
  2. Type the following command to add the PnP Reusable Controls library to your project:
    npm install @pnp/spfx-controls-react

    > Note: most instructions tell you to use npm install @pnp/spfx-controls-react --save --save-exact, but the --save parameter is considered obsolete now. Feel free to use what you feel most comfortable with.

  3. In your web part class (the one that extends BaseClientSideWebPart), add a property to store your web part's title. If your web part is called EditableTitle, you would open the EditableTitleWebPart.ts file, and look for the IEditableTitleWebPartProps interface (usually located at the top of the file. Add the following code:
    export interface IEditableTitleWebPartProps {
    description: string;
    // BEGIN Add to support web part title
    title: string;
    // END Add
    }
  4. In your render method, your web part will need to pass the web part title, the displayMode (to determine whether the web part is in Edit Mode or Read Mode), and a function to handle changes to the title. All these things get passed to the component that is responsible for rendering the body of your web part. Your new render method will look like this:

    public render(): void {
    const element: React.ReactElement<ieditabletitleprops> = React.createElement(
      EditableTitle,
      {
        description: this.properties.description,
        // BEGIN: Add to support web part title
        // Don't forget that you need to add a comma at the end of the previous line
        title: this.properties.title,
        displayMode: this.displayMode,
        updateTitle: (value: string) => {
          this.properties.title = value;
        }
        //END: Add
      }
    );
    
    ReactDom.render(element, this.domElement);
    }

    If you get a nasty error message when you add the code, don't worry. We just haven't defined a title, displayMode, and updateTitle property for your component yet.

    Error message in code
    Don't worry about this error message, we'll fix it soon

  5. Open your component's I[YourWebPartName]Prop interface (located under src\webparts\[YourWebPartName]\components\I[YourWebPartName]Props.ts). Since my web part is called editableTitle, I'll open src\webparts\editableTitle\components\IEditableTitleProps.ts and add an import for the DisplayMode at the top of your file:
    import { DisplayMode } from '@microsoft/sp-core-library';
  6. Add the properties your component will need to support the web part title in your I[YourWebPartName]Props interface:
    export interface IEditableTitleProps {
    description: string;
    //BEGIN: Add support for web part title
    title: string;
    displayMode: DisplayMode;
    updateTitle: (value: string) => void;
    //END: Add
    }
  7. Open the web part's component TSX file (located under src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx) and add an import for the WebPartTitle control:
    import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
  8. In your [YourWebPartName]'s render method, add the WebPartTitle control. You should add it immediately after the first opening <div>:
    public render(): React.ReactElement<ieditabletitleprops> {
    return (
      <div classname="{styles.editableTitle}">
        {/* BEGIN: Add to support web part title */}
        <webparttitle displaymode="{this.props.displayMode}" title="{this.props.title}" updateproperty="{this.props.updateTitle}">
        {/* END: Add */}
        <div classname="{styles.container}">
          <div classname="{styles.row}">
            <div classname="{styles.column}">
              <span classname="{styles.title}">Welcome to SharePoint!</span>
              <p classname="{styles.subTitle}">Customize SharePoint experiences using Web Parts.</p>
              <p classname="{styles.description}">{escape(this.props.description)}</p>
              <a href="https://aka.ms/spfx" classname="{styles.button}">
                <span classname="{styles.label}">Learn more</span>
              </a>
            </div>
          </div>
        </div>
      </webparttitle></div>
    );
    }

    Note: some people prefer to add the web part title within the second div -- the one with the container CSS class. I'll be removing that div in future code samples, but feel free to do as you please.

  9. Run gulp serve to test your web part and try editing your web part's title

The Web Part Title in action

Adding a default title

To make things easier for your users, you should provide a default title for your web part. Try to use a title that users won't immediately have to change (like Insert Title Here). For example, if your web part retrieves a list of recently added documents, try using Recent documents. If your web part displays events, try Upcoming events or Events.

Since the web part title is stored in a web part property (we called it title in our sample above), the easiest way to add a default title is to use your web part manifest's preconfiguredEntries to provide a default title.

To do so:

  1. Open the [YourWebPartName]WebPart.manifest.json file (located under the src\webparts\[YourWebPartName] folder)
  2. Locate the preconfiguredEntries JSON node
  3. Under the "properties" node, add the default web part title as follows:
    "properties": {
      "description": "EditableTitle",
      "title": "My Default Web Part Title"
    }
  4. Run gulp serve again. If your solution was already running, you need to stop the existing gulp serve and start a new one, otherwise, the updated manifest won't be reflected in your web part.
  5. If your web part was already running, remove it from the workbench, refresh the page, and re-add the web part. You should see your updated web part, now with a default title:

My Default Web Part Title

Adding a placeholder title

If your web part title is optional, or you prefer to start with a blank title, you should add a placeholder.

For example, the People web part uses People profiles for the placeholder title. It shows the title placeholder when your page is in Edit mode, but if you don't specify a title and save your page to view it in Read mode, the title will disappear.

People web part in edit mode shows placeholder
People web part in edit mode shows placeholder

People web part in reading mode hides placeholder
People web part in reading mode hides placeholder

To implement this functionality, follow the same steps you did to add the web part title, but don't provide a default title. Then:

  1. In the [YourWebPartName].tsx file, find your WebPartTitle component in the render method.
  2. Add the placeholder prop with the placeholder title you want to use.
    <webparttitle displaymode="{this.props.displayMode}" title="{this.props.title}" updateproperty="{this.props.updateTitle}" placeholder="{"Web" part="" placeholder"}="">

Note: please consider localizing your placeholder text. I'll explain localizing in a later post.

If you want to see the sample web part I'm building in this post, visit the GitHub repo and open the WebPartTitles solution. The web part is called PlaceholderTitle

How to add a description

Unfortunately, there isn't a WebPartDescription component that you can use to easily add a description section to your web part.

Fortunately, we can cheat and use the WebPartTitle component and apply some CSS magic to get the same results.

Here is how:

  1. If you haven't done so already, add the PnP Reusable Controls library to your project by typing the following command:

    npm install @pnp/spfx-controls-react
  2. In your web part class (located under src\webparts\[YourWebPartName]\[YourWebPartName]ts. Add a property to store your description. Since the default web part already has a description prop, I used webPartDescription, but in real life, I'd use the description prop for that purpose.

    export interface IEditableTitleWebPartProps {
    description: string;
    // BEGIN Add to support web part description
    webPartDescription: string;
    // END Add
    }
  3. In your render method, your web part will need to pass the web part description, the displayMode (to determine whether the web part is in Edit Mode or Read Mode), and a function to handle changes to the description. All these things get passed to the component that is responsible for rendering the body of your web part. Your new render method will look like this:

    public render(): void {
    const element: React.ReactElement<ieditabletitleprops> = React.createElement(
      EditableTitle,
      {
        description: this.properties.description,
        // BEGIN: Add to support web part description
        // Don't forget that you need to add a comma at the end of the previous line
        webPartDescription: this.properties.webPartDescription,
        displayMode: this.displayMode,
        updateDescription: (value: string) => {
          this.properties.webPartDescription = value;
        }
        //END: Add
      }
    );
    
    ReactDom.render(element, this.domElement);
    }
  4. Open your component's I[YourWebPartName]Prop interface (located under src\webparts\[YourWebPartName]\components\I[YourWebPartName]Props.ts). Since my web part is called webPartDescription, I'll open src\webparts\webPartDescription\components\IWebPartDescriptionProps.ts.

  5. If you haven't done so already, add an import for the DisplayMode at the top of your file:

    import { DisplayMode } from '@microsoft/sp-core-library';
  6. Add the properties your component will need to support the web part description in your I[YourWebPartName]Props interface:

    export interface IWebPartDescriptionProps {
    description: string;
    //BEGIN: Add support for web part description
    webPartDescription: string;
    displayMode: DisplayMode;
    updateDescription: (value: string) => void;
    //END: Add
    }
  7. Open the web part's component TSX file (located under src\webparts\[YourWebPartName]\components\[YourWebPartName].tsx).

  8. If you haven't done so already, add an import for the WebPartTitle control:

    import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
  9. In your [YourWebPartName]'s render method, add the WebPartTitle control where you would like the description to appear. We also add a placeholder (Add a description to override the default Web Part Title placeholder that comes with the WebParttitle component). In my sample, I'll add it below the container div:

    public render(): React.ReactElement<iwebpartdescriptionprops> {
    return (
      <div classname="{" styles.webpartdescription="" }="">
        <div classname="{" styles.container="" }="">
          <div classname="{" styles.row="" }="">
            <div classname="{" styles.column="" }="">
              <span classname="{" styles.title="" }="">Welcome to SharePoint!</span>
              <p classname="{" styles.subtitle="" }="">Customize SharePoint experiences using Web Parts.</p>
              <p classname="{" styles.description="" }="">{escape(this.props.description)}</p>
              <a href="https://aka.ms/spfx" classname="{" styles.button="" }="">
                <span classname="{" styles.label="" }="">Learn more</span>
              </a>
            </div>
          </div>
        </div>
         {/* BEGIN: Add to support web part description */}
         <webparttitle displaymode="{this.props.displayMode}" title="{this.props.webPartDescription}" updateproperty="{this.props.updateDescription}" placeholder="{"Add" a="" description"}="">
        {/* END: Add */}
      </webparttitle></div>
    );
    }
  10. To change the CSS for the web part description, we'll add some CSS. Open the [YourWebPartName].module.scss (located under src\webparts\[YourWebPartName]\components\[YourWebPartName].module.scss) and add the following CSS just above the .container class:

    .descriptionElement textarea {
    font-size: 14px;
    font-weight: 400;
    line-height: 1.6em;
    overflow-x: hidden;
    text-overflow: ellipsis;
    color: $ms-color-neutralPrimary;
    }
    
    .descriptionElement__NoMargin textarea  {
    margin-bottom: 0;
    }
    
    .descriptionElement__centerAlign textarea {
    text-align: center;
    }

    We're defining three CSS classes here: descriptionElement to render a smaller font, descriptionElement__NoMargin to remove the bottom margin, and descriptionElement_centerAlign to align the description at the center of the web part. You can use all three CSS styles, or only descriptionElement as it suits you.

  11. Go back to your [YourWebPartName].tsx.

  12. Add a reference to the css() function at the top of the file.

    import { css } from "@uifabric/utilities/lib/css";

    The css() function allows you to combine multiple CSS classes. To do so, I added. If you want to read more about the css() function, read my post.

  13. Add the CSS prop to the WebPartTitle you added in step 9. Use whatever combination of the three CSS classes we defined earlier as you desire (I use all three here):

         {/* BEGIN: Add to support web part description */}
         <webparttitle displaymode="{this.props.displayMode}" title="{this.props.webPartDescription}" updateproperty="{this.props.updateDescription}" placeholder="{"Add" a="" description"}="" classname="{css(styles.descriptionElement," styles.descriptionelement__nomargin,="" styles.descriptionelement__centeralign)="" }="">
        {/* END: Add */}
  14. Run gulp serve and test your web part.

You should get something that looks like this:

Web part description

Note: I really dislike the default shadowy border that comes with every Yeoman-generated SPFx web part because it isn't found anywhere else in the out-of-the-box SharePoint web parts. I always remove it from my projects. Although this is outside of the scope of this post, if you want to remove it, simply comment out the box-shadow line in the .container CSS class of your [YourWebPartName].module.scss. You'll get the following result:
Web parts without borders

If you want to see the code for this web part, visit the GitHub repo and open the WebPartTitles solution. The web part is called WebPartDescription. I know, I didn't think the web part name through when I built it.

UI text guidelines for Titles and descriptions

I've mostly copied this from the UI text guidelines for SharePoint web parts, but I include it here (and will continue to include the relevant sections throughout this series) because it is important to use consistent User Interface (UI) text within your web parts.

Capitalization

Use sentence casing (the first letter of the first word is capitalized, the rest all lowercase) for all titles.

Always capitalize:

  • The first word of a web part title or description
  • The word following a colon in a title. For example, "Step 1: Begin by entering your account information."
  • Proper nouns, such as the names of people, cities, and so on.

Punctuation

Don't use periods in titles. For other punctuation types, follow the basic rules of punctuation.

Voice and tone

If you want your users to fall in love with your web parts, make sure to use the right tone in your UI text. It will help build a strong, lasting relationship with your users.

Try to keep your words crisp and clear, warm and relaxed, and approachable.

Follow these simple tips:

  • Use a casual, conversational tone in the UI.
  • Use contractions. For example, use "can't" instead of "cannot".
  • Read your UI text out loud to test the tone. Does it sound like everyday language?
  • Use simple words.
  • Remove technical details if they're not relevant to the user experience.
  • Use "Please" only if you are inconveniencing the user. Avoid overuse.
  • Use "Sorry" only in error messages in SharePoint that result in serious problems for the customer.

Pronouns

Avoid pronouns if you can.

If you must use pronouns, follow these rules:

  • Use second person ("you" or "your") when you're presenting something that belongs to the user. For example, "Your drafts" or "Your images".
  • Use the first person ("me" or "my") for UI in which the user instructs the service to do something. For example, "Alert me when someone responds to my post."
  • Use "they" or "their" as a singular possessive modifier to avoid awkward "he/she" or "his/her" constructs. Ideally, rewrite the sentence as plural if possible.
  • Avoid using "them"; instead, use words like "someone" or "people". For example, "Enter a user name and domain to give someone permission to use this PC."

If you want to sound really cold and snobby, use third person references. Instead of saying "Users can change the layout", use a phrase like "You can change the layout".

Hint text

Hint text, or ghost text, is the text element you display in a UI element to help the user interact with the UI.

In the case of titles and descriptions, your hint text is your placeholder attribute. It should give information about what the user should enter.

You should try to use hint text sparingly, and only if it helps the user. Because titles and descriptions are not usually visible on a web part if they are empty, this is an instance where you should definitely use hint text.

Conclusion

This post described how to add titles and descriptions to your web parts in accordance with the SharePoint Design Principles.

In future posts, I'll explain the other design areas... but since the other design areas are a bit more complicated, I'll break each down into a few smaller posts.

I hope this helps?

Thanks

This post wouldn't have been possible without the amazing work from the SharePoint team. The technical writers don't often get the glory, so we should especially thank Linda Caputo and David Chestnut for their contributions.

More Information

I could have gone on for days about some design topics in this article, but I'll let the actual experts do the talking:

Update

  • July 12, 2019: Added the UI text section because it was driving me nuts that I had omitted it in the first place.
  • July 17, 2019: Fixed some embarassing spelling mistakes. I thought I was on a roll and writing well, but it turns out my spell checker was simply turned off.