There's a new version of Purple available. View the docs.

Introduction

Purple is a UI kit for all of Heroku's digital properties. Purple provides guidelines for the aesthetic, function, and form of user interfaces to provide a consistent experience for our customers. This is a living document and is under constant iteration.

Style kit

Style kit provides Purple styling for your application. Use Style kit if you do not wish to use Ember components or need a great deal of interactivity.

View Style kit

Interaction kit

Interaction kit provides a suite of components that are created to match the look & feel of Style kit and follow the Purple design guidelines & patterns.

Coming soon

Guidelines & patterns

General design guidelines for Purple to create a harmonious user experience regardless of the technical stack of your application.

View guidelines & patterns

Installation

  • Purple is an internal Heroku tool that we hope provides inspiration. It is publicly documented in order to illustrate our design philosophy and process.
  • Purple should never be used outside of officially endorsed Heroku products or without explicit permission.
  • For more information about the Heroku trademark, please see our trademark policy page.

The Purple source code and implementation details are limited to internal Heroku employees.

Herokai can find installation instructions in the private repo.

Writing CSS

For our own components, follow the BEM (Block, Element, Modifier) method for writing & organising CSS. BEM helps keep our CSS logical and predictable. The naming convention follows this pattern:

.block {}
.block__element {}
.block__element--modifier {}
  • .block represents the higher level of an abstraction or component.

  • .block__element represents a descendent of .block that helps form .block as a whole.

  • .block--modifier represents a different state or version of .block.

(Source: CSSWizardry)

For more information on BEM, please see this article.

Color

Shades of purple-hinted greys combined with the Heroku brand color.
Pastel primary colors for out of the ordinary aspects and events that require attention.

Brand color
#79589F
$brand-primary
Hero color
#534A93


Highlight
#56CDFC
$brand-info
Success
#74C080
$brand-success
Warning
#FA9F47
$brand-warning
Error
#D64242
$brand-danger


Gray lighter
#E7E7EC
$gray-lighter
Gray light
#CBCBD2
$gray-light
Gray
#7D7D8E
$gray
Gray dark
#3F3F44
$gray-dark

Typography

Benton Sans throughout. Consistent weights, purple for major headings, shades of grey and dark grey for everything else.

h1. Purple heading

h2. Purple heading

h3. Purple heading Secondary text

h4. Purple heading Secondary text

h5. Purple heading Secondary text
h6. Purple heading Secondary text
<h1>h1. Purple heading</h1>
<h2>h2. Purple heading</h2>
<h3>h3. Purple heading <small>Secondary text</small></h3>
<h4>h4. Purple heading <small>Secondary text</small></h4>
<h5>h5. Purple heading <small>Secondary text</small></h5>
<h6>h6. Purple heading <small>Secondary text</small></h6>

Buttons

Standard

<button type="button" class="btn btn-default">Default</button>
<button type="button" class="btn btn-default"><i class="icon icon-fav"></i> Icon</button>
<button type="button" class="btn btn-primary">Default</button>
<button type="button" class="btn disabled">Default</button>
<button type="button" class="btn btn-link">Default</button>

Large (lg)

<button type="button" class="btn btn-default btn-lg">Default</button>
<button type="button" class="btn btn-default btn-lg"><i class="icon icon-fav"></i> Icon</button>
<button type="button" class="btn btn-primary btn-lg">Default</button>

Small (sm)

<button type="button" class="btn btn-default btn-sm">Default</button>
<button type="button" class="btn btn-default btn-sm"><i class="icon icon-fav"></i> Icon</button>
<button type="button" class="btn btn-primary btn-sm">Default</button>

Extra Small (xs)

<button type="button" class="btn btn-default btn-xs">Default</button>
<button type="button" class="btn btn-default btn-xs"><i class="icon icon-fav"></i> Icon</button>
<button type="button" class="btn btn-primary btn-xs">Default</button>

Colors

<button type="button" class="btn btn-danger">Default</button>
<button type="button" class="btn btn-default btn-danger">Default</button>
<button type="button" class="btn btn-success">Default</button>
<button type="button" class="btn btn-default btn-success">Default</button>

Button Groups

<div class="btn-group">
  <button type="button" class="btn btn-default">Left</button>
  <button type="button" class="btn btn-default">Middle</button>
  <button type="button" class="btn btn-default">Right</button>
</div>
<div class="btn-group">
  <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
    Action <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" role="menu">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>
<div class="btn-group">
  <button type="button" class="btn btn-default">Action</button>
  <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
    <span class="caret"></span>
    <span class="sr-only">Toggle Dropdown</span>
  </button>
  <ul class="dropdown-menu" role="menu">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li class="divider"></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>

Grid system

Purple uses the standard Bootstrap grid which is a 12 column responsive layout.

Three equal columns

.col-md-4
.col-md-4
.col-md-4

Three equal columns (down to mobile)

.col-xs-4
.col-xs-4
.col-xs-4

Three unequal columns

.col-md-3
.col-md-6
.col-md-3

Two columns

.col-md-8
.col-md-4

Forms

Basic Example

<div class="purple-box u-padding-Al">
  <form role="form">
    <div class="form-group">
      <label for="exampleInputEmail1">Email address</label>
      <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
    </div>
    <div class="form-group">
      <label for="exampleInputPassword1">Password</label>
      <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
    </div>
    <div class="checkbox">
      <label>
        <input type="checkbox"> Check me out
      </label>
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
  </form>
</div>

Inline Form

<div class="purple-box u-padding-Al">
  <form class="form-inline" role="form">
    <div class="form-group">
      <label class="sr-only" for="exampleInputEmail2">Email address</label>
      <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
    </div>
    <div class="form-group">
      <label class="sr-only" for="exampleInputPassword2">Password</label>
      <input type="password" class="form-control" id="exampleInputPassword2" placeholder="Password">
    </div>
    <div class="checkbox">
      <label>
        <input type="checkbox"> Remember me
      </label>
    </div>
    <button type="submit" class="btn btn-primary u-margin-Lm">Sign in</button>
  </form>
</div>

Horizontal Form

<div class="purple-box u-padding-Al">
  <form class="form-horizontal" role="form">
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <label class="ember-view radio-inline">
          <input type="radio" value="us" name="region" checked="checked">United States
        </label>

        <label class="radio-inline">
          <input type="radio" value="eu" name="region">Europe
        </label>
      </div>
    </div>

    <div class="form-group">
      <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
      <div class="col-sm-10">
        <input type="email" class="form-control" id="inputEmail3" placeholder="Email">
      </div>
    </div>

    <div class="form-group">
      <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
      <div class="col-sm-10">
        <input type="password" class="form-control" id="inputPassword3" placeholder="Password">
      </div>
    </div>

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <div class="checkbox">
          <label>
            <input type="checkbox"> Remember me
          </label>
        </div>
      </div>
    </div>

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button type="submit" class="btn btn-default">Sign in</button>
      </div>
    </div>
  </form>
</div>

Button Addons

<div class="purple-box u-padding-Al u-padding-Bm">
  <div class="form-group">
    <div class="input-group">
      <input type="text" class="form-control" placeholder="Enter a deploy message">
      <span class="input-group-btn">
        <button class="btn btn-default" type="button">Deploy...</button>
      </span>
    </div>
  </div>

  <div class="form-group">
    <div class="input-group">
      <input type="text" class="form-control" placeholder="Enter a deploy message">
      <span class="input-group-btn">
        <button class="btn btn-primary" type="button">Deploy...</button>
      </span>
    </div>
  </div>
</div>

Supported Controls



A block of help text that breaks onto a new line and may extend beyond one line.

Validation

Input states

.form-group has-success
.form-group has-error
.form-group has-warning

Form states

Forms should show granular errors as well as an alert at the top of the form, to alert the user to errors.

There was one error submitting this form

Alerts

Standard error

Your profile could not be updated

<div class="alert alert-danger">
  <p>Your profile could not be updated</p>
</div>

Error with link

Your profile could not be updated. Do action

<div class="alert alert-danger">
  <p>Your profile could not be updated. <a href="#" class="text-right">Do action</a></p>
</div>

Standard success

floating-tundra-81 has been created. Edit this app

<div class="alert alert-success">
  <p><b>floating-tundra-81</b> has been created. <a href="#" class="text-right">Edit this app</a></p>
</div>

Standard info

A new update is available for your app

<div class="alert alert-info">
  <p>A new update is available for your app</p>
</div>

Messaging

Muted message

Primary message

Success message

Info message

Warning message

Danger message

Primary message

Success message

Info message

Warning message

Danger message

Modal

<!-- Button trigger modal -->
<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
  Launch Modal
</button>

<!-- Modal -->
<div class="modal" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
        <h4 class="modal-title" id="myModalLabel">Modal Title</h4>
      </div>
      <div class="modal-body">
        Content
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary">Save Changes</button>
      </div>
    </div>
  </div>
</div>

Sub nav

<div class="nav nav-tabs sub-nav app-nav">
  <a href="#" class="active">
    <i class="icon icon-fav"></i>
    <span>Resources</span>
  </a>

  ...

  <a href="#">
    <i class="icon icon-fav"></i>
    <span>Settings</span>
  </a>
</div>

Lists

Unordered List

  • Unordered list item
  • Another unordered list item
    • Nested list item one
    • Another nested list item
  • Short list item

Ordered List

  1. Ordered list item
  2. Another ordered list item
  3. Short list item

Definition Lists

Euismod
Vestibulum id ligula porta felis euismod semper eget lacinia odio sem nec elit.
Malesuada porta
Etiam porta sem malesuada magna mollis euismod.
Euismod
Vestibulum id ligula porta felis euismod semper eget lacinia odio sem nec elit.
Malesuada porta
Etiam porta sem malesuada magna mollis euismod.

List Group

  • Dapibus ac facilisis in
  • Morbi leo risus
  • Porta ac consectetur ac
  • Vestibulum at eros

Tables

Basic example without a header

1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
<table class="table">...<table>

Basic example

# First Name Last Name Username
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
<table class="table table-hover">...<table>

Striped rows

# First Name Last Name Username
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
4 Jacob Thornton @fat
5 Larry the Bird @twitter
<table class="table table-striped">...<table>

Bordered table

# First Name Last Name Username
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
<table class="table table-bordered">...<table>

Condensed table

# First Name Last Name Username
1 Mark Otto @mdo
2 Jacob Thornton @fat
3 Larry the Bird @twitter
<table class="table table-condensed">...<table>

Spinner

Default spinner

For displaying the loading state of a view

<div class="spinner">
  <i class="spinner__dot spinner__dot--one"></i>
  <i class="spinner__dot spinner__dot--two"></i>
  <i class="spinner__dot spinner__dot--three"></i>
</div>

Inverted spinner

For dark and purple backgrounds

<div class="spinner spinner--inverted">
  <i class="spinner__dot spinner__dot--one"></i>
  <i class="spinner__dot spinner__dot--two"></i>
  <i class="spinner__dot spinner__dot--three"></i>
</div>

Button spinner

To show the working state of a button action e.g. saving or updating

<a class="btn btn-sm btn-default disabled btn--saving">
  <span class="spinner">
    <i class="spinner__dot spinner__dot--one"></i>
    <i class="spinner__dot spinner__dot--two"></i>
    <i class="spinner__dot spinner__dot--three"></i>
  </span>
  Saving
</a>

Utilities

Space

Spacing utilities allow you to insert generic spacing to elements and components.
The classes take the following form: .u-{{type}}-{{direction}}{{size}}

Types

  • margin
  • padding

Directions

  • A — all
  • T — top
  • R — right
  • B — bottom
  • L — left
  • H — horizontal
  • V — vertical

Sizes

  • n — none
  • xxs — 6px
  • xs — 9px
  • s — 12px
  • m — 20px
  • l — 27px
  • xl — 33px

Examples

.u-margin-Bl .u-padding-Al

Large bottom margin & large padding

.u-padding-An

No padding

State

Helpers for the state of objects

  • .u-is-hidden — display none & remove from flow (screenreaders)
  • .u-is-visible — display as block
  • .u-is-hidden-visually — hide visually but visible to screenreaders
  • .u-is-visible — visible
  • .u-is-invisible — invisible
  • .u-is-actionable — adds cursor pointer
  • .u-is-draggable — adds cursor move

Blank state

The blank state of a view is what users will see before it is populated with data. For the collaborators view, for example, this would be before any collaborators have been added.

The blank state should answer the following questions:

  • What will appear in the view?

  • What is the point of this view?

  • How do I add things to this view?

  • How do I learn more about this? (If the concept cannot be fully explained in one or two sentences)

Good

Collaborators will appear here

People you invite as collaborators have code & deploy access to this app

Add a collaborator

Bad

You have 0 collaborators

CRUD Create, update, destroy patterns

Adding new data

In Dashboard we have three levels of additive change:

  • Convenience – the form to make the change is always visible (Collaborators)

  • Disclosed – the form to make changes is hidden until requested (SSH keys)

  • Complex – changes are made within a modal (2FA, Credit cards)

Convenience

Convenient, always visible forms should be used when the input form is very simple (one or two elements) and where it does not distract from the content of the view.

The form to add a new collaborator is always visible

Disclosed

Disclosed forms should house more complex inputs (such as multiple text inputs, text areas, radio buttons etc) or can be used where adding data is an uncommon/infrequent function of the view.

The form to add an SSH key is hidden until the user discloses it

Complex

Complex forms may span multiple pages (wizards) or may contain other content that merits an entirely focused view. These may be shown on a new page or within a modal.

Inline editing

For single inputs (like name, email address in our profile), the input can be put in an editable state just by clicking on it.

Listviews

Editing rows

If an item in a list view is editable, the pencil icon should be present next to it. Clicking the edit icon opens a modal that allows the user to perform actions on the item. Validation should also happen in this modal.

The modal dialog lists all editable columns vertically and allows the user to update or discard their changes.

Removing rows

Removing a row happens in the same locus as editing. Clicking the delete icon displays a confirmation modal. The most distinguishable detail of the row (e.g. Name for Collaborator, Add-on name for add-on) should be used in the modal so the user is sure they're deleting the correct item.

The modal should use the confirmation modal pattern.

Confirmation Destroying data forever

If the user is destroying significant data that cannot be restored, they should have to pass a confirmation dialog. This should not be used for any destructive action, as it’s designed to interrupt the user.

Examples of appropriate use

  • Removing add-ons

  • Transferring an app

  • Deleting an app

Confirmation with app name input

The user confirms an action by retyping the name of the app they wish to modify.

Simple confirmation

The user is presented with a modal dialog to confirm or cancel a modification.