Volue logo
Volue logomark
Wave Core
Show Hide
v1.14.1 16-10-2024
Jump to section

Grid

Component for creating responsive, fluid and nestable grid layout

The class .grid collapses whitespace and makes children inline-block. This lets us use all text-properties (like text-align, vertical-align, white-space) to generate complex layouts. Any element can be a grid-cell.

Grid automatically wrap cells across multiple lines when they don't fit a single row. Also, you can add the .grid--border modifier, to have nicely collapsed borders as shown below:

grid-cell w20
extra content

Use .w100 helper to render cells at fill-width:

Grid supports nesting:

1
Nested 1
Nested 2

Build the grid using mobile-first approach: treat mobile as your default and build more complex layouts up from there. --m and --l modifiers indicate responsive breakpoints. First cell will span full width on mobile, 70% width at the medium breakpoint, and 30% at the large breakpoint. See width helpers.

Grid arranges cells of different heights into clear rows. You can use alignment helpers to control vertical alignment of individual cells:

1
2
3
4
1 vaM
2 vaM
3
4 vaB

You can automatically add gutters (in the form of padding around the cells) by applying .grid--withGutter modifier. Note that this can trigger a horizontal scrollbar if the grid is as wide as the viewport. Use padding on a container to protect against it.

Use .grid--reverse to reverse rendered order of grid cells. Grid cells 1, 2, 3 in the markup will display in order 3, 2, 1:

1
2
3

Use .grid--center to center-align all grid cells except their content:

Use .grid--right to right-align all grid cells except their content:

Use .grid--justify to justify-align all grid cells except their content. Excess horizontal space gets distributed among the gaps:

Use .grid-cell--center to set a specific cell to be horizontally centered:

.grid--arrange makes use of CSS table layout. Use it to arrange rows of equal-height cells. If you don't specify cell widths, they'll render equal-width.
.grid--arrange is very useful when building form layouts, as each cell in a row has the same height. This allows us to ditch cumbersome JavaScript solutions.

Equal height cells with border
ancetta chicken shank ribeye leberkas jerky beef ribs tri-tip strip steak short ribs bacon capicola. Fatback strip steak ribeye meatloaf swine kevin cow tail turkey bacon shank shankle frankfurter biltong chuck. Andouille beef ribs leberkas, frankfurter venison ground round swine landjaeger tri-tip.
Equal height cells with border
Equal height cells with border and middle-aligned content
Equal height cells with border
ancetta chicken shank ribeye leberkas jerky beef ribs tri-tip strip steak short ribs bacon capicola. Fatback strip steak ribeye meatloaf swine kevin cow tail turkey bacon shank shankle frankfurter biltong chuck. Andouille beef ribs leberkas, frankfurter venison ground round swine landjaeger tri-tip.
Equal height cells with border
ancetta chicken shank ribeye leberkas jerky beef ribs tri-tip strip steak short ribs bacon capicola. Fatback strip steak ribeye meatloaf swine kevin cow tail turkey bacon shank shankle frankfurter biltong chuck. Andouille beef ribs leberkas, frankfurter venison ground round swine landjaeger tri-tip.
Equal height cells with border and bottom-aligned content

Extend with .grid--position if you want the grid to stay centered within the bounds of a container. The container should be positioned, which means it should either have position: relative, position: absolute or position: fixed on it. Position of the grid is determined by its container's dimensions (width & height).

.grid--position doesn't play well with .grid--justify modifier.

Container

To set width limit and center you can wrap your layout in a containing div with a class of wrap:

wrap
Adds 28px padding, will shrink but not grow beyond 600px

Nav

Component for various navigational constructs

When used on an <ol> or <ul>, .nav throws the list into horizontal mode:

Use .nav--stacked to throw the list into vertical mode:

Use .nav--block to give nav links a big, blocky clickable area with hover state. .is-active class highlights link:

Hover-state semi-transparent backgrounds are automatically derived from the parent's text color:

Combine .nav--block with .nav--rounded to make nav links corners rounded:

Combine .nav--block with .nav--tabs to create a tab-style navigation:

Simple navbar that collapses for smaller viewports:


List block

Component for presenting multiple items in a vertical arrangement

List block is a very versatile component and can hold content ranging from basic text to icons, buttons and UI controls.

The majority of space on a list item should be dedicated to the primary action (i.e. selecting an item). Text within a list item should be considered part of the primary action target. You can place supplemental action represented via, for example, icon on the right side of a list item. Supplemental actions are always a separate target from the primary action.

In some cases the primary distinguishing content may not be actionable. You can place a button on the right side of the item to represent a related action:

  • Item one Bacon ipsum dolor sit amet chuck prosciutto landjaeger ham hock filet mignon shoulder hamburger pig venison
  • Item two Bacon ipsum dolor sit amet chuck prosciutto landjaeger ham hock filet mignon shoulder hamburger pig venison

Definition list

Component for displaying name/value pairs

Useful for glossaries and such.

Owner
John Doe
Contact information
John Doe
Phone
+47 22222424
Text
Fusce vitae dologr a nulla rhoncus vulputate sed nec mauris. Mauris condimentum pretium arcu sed molestie. Suspendisse posuere vitae sapien sit amet tristique. Praesent non est augue. In hac habitasse platea dictumst. Curabitur quis diam ante. Ut at vestibulum leo. Integer porta posuere tincidunt. Mauris at eleifend risus.

Use .definitionList--striped to render striped definition list:

Owner
John Doe
Contact information
John Doe
Phone
+47 22222424
Text
Fusce vitae dologr a nulla rhoncus vulputate sed nec mauris. Mauris condimentum pretium arcu sed molestie. Suspendisse posuere vitae sapien sit amet tristique. Praesent non est augue. In hac habitasse platea dictumst. Curabitur quis diam ante. Ut at vestibulum leo. Integer porta posuere tincidunt. Mauris at eleifend risus.

Buttons

Buttons are transparent and sized by content, but can also be combined with color styles and sizing helpers. They can include an icon, text, or both. Mainly buttons and a-elements and input-labels (described in forms-section) are used as buttons, to make sure we follow focus-requirements in the WCAG spesification ›

Primary button

Use this type of button if you have main action(s) that you want to promote to the user.

If the user have to choose between primary and secondary action, consider using outline button or ghost button for the secondary action.

Link styled as primary button Primary button with too much text that overflows

Outline (secondary) button

This type of button can be used as an alternative to the primary button if the actions requires less focus or attention.

Can also be used next to the primary button, if there is a secondary action.

Link styled as outline button

Ghost (tertiary) button

This type of button, similarly to outline button, can be used for less-pronounced actions. Because ghost buttons don’t have a container, they don’t distract from nearby content.

Link styled as ghost button

Button groups

You can group together buttons related by function. A button group is a series of buttons laid out next to each other, joined together to create one continuous UI.

Button groups are useful to create "toggles" that allow to switch between two or more options. This can be used to filter content for example.

Using flexbox helpers, the items within a group can be justified:

Success/danger buttons

You can add .btn--success or .btn--danger modifiers to imply the consequence of the button's action:

Buttons with lone icons

Buttons with lone icon should have .btn--icon modifier alongside .btn class. Because the button with lone icon doesn’t have a label, use a <span class="txtAssistive"> with descriptive text or include aria-label attribute for accessibility reasons:

Inverse buttons

Buttons can come in inverse variation, for instance when used on a dark background:

Button states

You can apply disabled state for a button either using .is-disabled class or disabled attribute. disabled attribute works only for form elements, i.e. <button>. If you want to disable other btn elements, use .is-disabled class.

Disabled button

Buttons can have waiting state:


Icons

See Icons page for the list of available icons and comprehensive guide on usage.

Symbol in header

To show circle outlined symbols with medium size and button style with hover effect, use the .iconCircle class with .iconCircle--outline and .iconCircle--medium modifiers. This will make the symbol double size, and add some extra padding.


Labels

Component for labelling content with certain keywords, showing unread content, highlighting new content and displaying status

Standard labels are great for drawing attention without visual overload.

Label Label Label Label Label Label
Label Label Label Label Label Label

Labels are available in outline variation. Outlined labels have more subtle appearance.

Label Label Label Label Label Label

Labels can also be themed using color helpers.

Label Label

A label can be circular. Circular labels are used for counts, so should only contain numbers:

2 4 12 442

Labels can come in small size:

Label Label 2

Labels can be used within block lists, buttons, navigation and more:

Heading New

  • Mango Fruit
  • Poodle Dog 5
  • Item one In progress
  • Item two Moved

Kbd

Component for showing which key or combination of keys performs a given action.

shift + H

alt or option

⌘D

⌘⇧U


Avatar

Component for representing people or objects

Avatars support images and profile pictures:

If the user hasn't uploaded profile picture, you may use their initials (up to two letters) for avatars.

If the record name contains two words (e.g. first and last name) use the first capitalized letter of each. For records that only have a single word name, use the first two letters of that word using one capital and one lower case letter.

When an image is unavailable (for example, due to an error) or if the initials cannot be rendered, a placeholder silhouette icon should be shown instead:

Avatars support presence indicators. You can use active, busy or away modifiers for different status colors. The default is gray which means offline.

Avatars are available in 4 sizes to accommodate different use cases and can be themed using fill helpers:

Avatars can be grouped together in a stack to show for example multiple contributors:


Notification

Notifications show up in task flows, to prominently communicate important information to the user. They usually appear close to the specific area in the UI needing attention.

This notification details some information.
This notification details some warning information.
This notification details some error information.
This notification details some success information.

Notifications can come in small size, with help of .notification--small modifier.

This notification details some information.
This notification details some success information.

Notifications can optionally include a dismiss button for messages that can be closed and a contextual action to take. The action content will be displayed on the end side of the notification, except when screen size is small in which case the layout will stack vertically to better accommodate the content.

This feature is still under development.

Notification supports switching layout direction to vertical with .notification--vertical modifier. It can come handy when a message longer than two lines is required and/or you want to include multiple action elements.

This notification details a large amount information that wraps into more than two lines, forcing the height of the notification to be larger. For this kind of multi-line notifications, vertical layout with actions below the message is optimal.

Notifications that appear as temporary, dismissable popups (toast notifications) or those displayed in a dialog overlay, should use .notification--headless modifier class to remove default outline styles. These type of notifications are generally used for communicating feedback on a user’s action.

New version of the application is available.
Request failed!
Changes successfully saved!

Use modal component to show notification dialogs on top of the application and to confirm or request action from the user:

var warningMessage = [
  '<div class="notification notification--warning notification--headless notification--vertical">',
    '<div class="notification-icon">',
      '<div class="svgIcon svgIcon--stroked">',
        '<svg focusable="false" aria-hidden="true">',
          '<use xlink:href="#svg--warning" />',
        '</svg>',
      '</div>',
    '</div>',
    '<div class="notification-message">',
      '<h3 class="delta">Delete data?</h3>',
      '<p class="mTs mBn fg-neutral--moderate">Are you sure you want to delete this data? This action cannot be undone.</p>',
    '</div>',
    '<div class="notification-actions dF aiC">',
      '<button type="button" class="btn btn--danger mRs js-modalAction">Delete</button>',
      '<button type="button" class="btn btn--outline js-modalClose">Cancel</button>',
    '</div>',
  '</div>'
].join('\n');

var warningDialog = Wave.Modal({
  content: warningMessage,
  contentClass: 'pAn bdr--noRounding',
  closable: false,
  modalAction: function (e) {
    e.preventDefault();
    console.log('[Wave.Modal] time changed');
    this.hide();
  }
});

$(document.querySelector('.js-triggerWarning')).on('click', function (e) {
  e.preventDefault();
  warningDialog.show();
});

Notification toaster

Provide feedback to the user through a timed popup

Notification toaster is handy for providing simple feedback or grabbing the user's attention, for operations or actions that he or she has performed. Toast notifications are self-dismissing by default and they do not interrupt the current activity. Multiple toast notifications can be shown at the same time stacking top-to-bottom. You should realize that due to transient nature of toast notifications, the user might not see them. Don't use them for critical level notifications and those that require action from the user.

Customizable options

var toaster = Wave.Toaster({
  position: 'topLeft',     // where to position notifications. Available options: 'topLeft', 'topCenter', 'topRight', 'centerLeft', 'centerRight', 'bottomLeft', 'bottomCenter', 'bottomRight' ('bottomLeft' is default)
  animation: 'slideDown',  // animation to use when notifications are added. Available options: 'slideUp', 'slideDown', 'slideLeft', 'slideRight', 'fade' ('slideUp' is default)
  wrapperClass: '',        // any additional class to append to the notifications wrapper
  duration: 6000,          // number in miliseconds before a notification disappears (default is 5000)
  template: ''             // individual notification markup (String), defaulting to '<div class="toaster-item"></div>' or based on type option (see below)
  type: undefined          // individual notification type. When specified the template for specific type will be used automatically. Available types:
                           // Wave.Toaster.TOAST_TYPES.SUCCESS,  Wave.Toaster.TOAST_TYPES.ERROR,  Wave.Toaster.TOAST_TYPES.INFO,  Wave.Toaster.TOAST_TYPES.WARNING
  closable: false          // when type is provided, controls whether dismiss functionality should be added to the notification
  action: {                // optional action definition
    label: '',             // label of the action
    onAction: function(toastElement, event) {}   // callback function to trigger when action is triggered by the user
  }
});

Component's API

.create(content, options) creates a toast notification, sets the content (HTML code or simply a non formatted string) and initializes timers. Original options can be customized by passing a second argument, which, for example, allows for setting variable types or timeouts of individual notifications.

.getElements() gets the list of notification nodes as an array.

.getElementsById() gets the dictionary of notification nodes by their unique identifiers.

.show(element) shows the given notification (called automatically after the notification is created).

.hide(element) hides the given notification and removes it after the transition is complete (called automatically when the duration is over).

.on('create', function(element)) event that triggers after a notification is created but before it's shown.

.on('showing', function(element)) event that triggers when the show() method is called.

.on('show', function(element)) event that triggers after a notification is shown.

.on('hiding', function(element)) event that triggers when the hide() method is called.

.on('hide', function()) event that triggers after a notification is hidden and removed.

Usage example

var toaster = Wave.Toaster({
  position: 'topLeft',
  animation: 'slideDown'
});

toaster.on('show', function (element) {
  console.log('[Wave.Toaster] notification shown');
});

$(document.querySelector('.js-createToast')).on('click', function (e) {
  e.preventDefault();
  toaster.create('This is an info toast notification.',  { type: Wave.Toaster.TOAST_TYPES.INFO });
});

$(document.querySelector('.js-createSlideRightToast')).on('click', function (e) {
  e.preventDefault();
  toaster.create('This is a success toast notification with slideRight animation.', {
    animation: 'slideRight',
    type: Wave.Toaster.TOAST_TYPES.SUCCESS
  });
});

$(document.querySelector('.js-createDismissableToast')).on('click', function (e) {
  e.preventDefault();
  toaster.create('This is a warning toast notification that will persist until dismissed.', {
    duration: 0,
    type: Wave.Toaster.TOAST_TYPES.WARNING,
    closable: true
  });
});

$(document.querySelector('.js-createToastWithAction')).on('click', function (e) {
  e.preventDefault();
  toaster.create('Request failed!', {
    duration: 0,
    type: Wave.Toaster.TOAST_TYPES.ERROR,
    closable: true,
    action: {
      label: 'Try again',
      onAction: function(toastElement) {
        alert('Toast action!');
        toaster.hide(toastElement);
      }
    }
  });
});

$(document.querySelector('.js-createToastWithCustomTemplate')).on('click', function (e) {
  e.preventDefault();
  toaster.create('This is a toast with custom template.', {
    template: '<div class="toaster-item fill-neutral--minimal bdrAm pAm"></div>'
  });
});

Modal

Component for displaying dialog prompts

Customizable options

var modal = Wave.Modal(
  {
    content: '',                  // the content to be shown inside a modal (String | HTMLElement)
    contentClass: '',             // any additional class to append to the content wrapper
    closable: false,              // whether the user can close a modal (Boolean)
    modalAction: function(e) {}   // callback function to run after action trigger element (.js-modalAction) is clicked
  }
);

Component's API

.show() shows the modal.

.hide(data) hides the modal. Accepts optional data argument that will be passed to the hide event.

.content(content) sets a new content for the modal.

.$(selector) returns DOM node inside the modal that matches the specified selector (String).

.on('showing', function()) event that triggers when the show() method is called.

.on('show', function()) event that triggers after transitions on the modal are finished (modal is shown).

.on('hiding', function()) event that triggers when the hide() method is called.

.on('hide', function(data)) event that triggers after transitions on the modal are finished (modal is destroyed and hidden). Callback is passed an object with wasDismissed property and merged data, if any, passed to hide() method by the user.

.on('content', function(content)) event that triggers when the new content is set.

Usage example

var modal = Wave.Modal({ content: '<h2>Hello world</h2>' });

var modalWithActionsContent = [
  '<div class="pTm pLl pRm dF jcSB">',
    '<h1 class="beta">Modal header</h1>',
    '<button type="button" aria-label="Close" class="btn btn--small btn--ghost btn--fullRounded mAn js-modalClose">',
      '<span class="svgIcon svgIcon--medium svgIcon--stroked">',
        '<svg aria-hidden="true" focusable="false">',
          '<use xlink:href="#svg--close"></use>',
        '</svg>',
      '</span>',
    '</button>',
  '</div>',
  '<div class="pTm pBl pHl">',
    '<p class="mAn">Sit nulla est ex deserunt exercitation anim occaecat. Nostrud ullamco deserunt aute id consequat veniam incididunt duis in sint irure nisi. Mollit officia cillum Lorem ullamco minim nostrud elit officia tempor esse quis. Cillum sunt ad dolore quis aute consequat ipsum magna exercitation reprehenderit magna. Tempor cupidatat consequat elit dolor adipisicing.</p>',
  '</div>',
  '<div class="modal-footer fill-neutral--moderate pVm pHl txtR">',
    '<button type="button" class="btn btn--ghost mAn mRm js-modalClose">Cancel</button>',
    '<button type="button" class="btn mAn js-modalAction">Save</button>',
  '</div>'
].join('\n');
var modalWithActions = Wave.Modal({
  content: modalWithActionsContent,
  contentClass: 'pAn measure',
  modalAction: function(e) {
    e.preventDefault();
    console.log('[Wave.Modal] action clicked');
    this.hide();
  }
});

$(document.querySelector('.js-showModal')).on('click', function (e) {
  e.preventDefault();
  modal.show();
});
$(document.querySelector('.js-updateModalContent')).on('click', function (e) {
  e.preventDefault();
  modal.content('<h2>Hallo verden</h2>');
});
$(document.querySelector('.js-showModalWithActions')).on('click', function (e) {
  e.preventDefault();
  modalWithActions.show();
});

Use .js-modalClose class on any element inside the modal that should trigger the close/hide action:

<a href="#" class="js-modalClose">Close Modal</a>
<button class="js-modalClose">Close Modal</button>

Use .js-modalAction class on any element inside the modal that should trigger the modalAction callback:

<a href="#" class="js-modalAction">Trigger modal action</a>
<button class="js-modalAction">Trigger modal action</button>
<button class="js-showModalWithActions">Show modal with actions</button>

If you want to set focus to specific element inside the active modal use autofocus attribute:

<input type="text" autofocus />

You can also populate modals with any in-page content. Add class .js-triggerModal, along with data-target attribute on the element that should trigger the modal. data-target should use the valid id or class name of associated in-page element which contents is to be displayed in the modal.

Show help

Modal component can be easily customised (see popup notifications).


Overlay

Create overlays over elements

Overlays are generated dynamically using JavaScript, so they don't require any markup. You can combine overlays with spinner and toggle components.

Customizable options

var overlay = Wave.Overlay(
  element,              // a DOM node to which to append the overlay (defaulting to document.body)
  {
    closable: true,     // whether the overlay is closeable by clicking on it
    overlayClass: '',   // any additional class to append to the overlay
    zIndex: 100         // set the stack order of the overlay (default is 10)
  }
);

Component's API

.show() shows the overlay.

.hide() hides the overlay.

.destroy() cleans up instance data and detaches event listeners.

.on('showing', function()) event that triggers when the show() method is called.

.on('show', function()) event that triggers after transitions on the overlay are finished (overlay is shown).

.on('hiding', function()) event that triggers when the hide() method is called.

.on('hide', function()) event that triggers after transitions on the overlay are finished (overlay is hidden and destroyed).

.on('click', function()) event that triggers when the overlay is clicked.

Usage example

var overlay = Wave.Overlay({
  closable: true,
  zIndex: 1000
});

overlay.on('click', function() {
  overlay.hide();
});

$(document.querySelector('.js-showOverlay')).on('click', function(e) {
  overlay.show();
  e.preventDefault();
});

Spinner

Spinning loaders to let the user know that a background process is being executed

Spinners are based on CSS3 transitions and animations. They will degrade gracefully on IE9 (just no animation).

Spinners are generated dynamically using JavaScript, so they don't require any markup. You can combine spinners with the overlay component to disable interactions while loading something.

Customizable options

var spinner = Wave.Spinner(
  element,                    // a DOM node to which to append (or prepend) the spinner
  {
    position: 'fixed',        // can choose "absolute", "relative", "fixed", "static"
    offset: '50%',            // offset from the chosen element (e.g. "20px" or "50%")
    color: 'fg-inverse'       // color you want to use ("fg-accent--moderate" is default)
    inline: false,            // whether the spinner is inline or not (false is default)
    insertionType: 'append'   // "prepend" for adding the spinner as a first child or "append" for adding as a last child ("append" is default)
  }
);

Component's API

.show() shows the spinner.

.hide() hides the spinner.

.isShown() returns a boolean specifying if the spinner is shown.

.on('show', function()) event that triggers when the spinner is shown.

.on('hide', function()) event that triggers when the spinner is destroyed and hidden.

Usage example

var spinner = Wave.Spinner(document.querySelector('.js-spinner'), {
  position: 'absolute',
  offset: '50%'
}).overlay({ closable: false });

spinner.show();

Chain with .overlay() to combine the spinner with the overlay component.

Bacon ipsum dolor sit amet chuck prosciutto landjaeger ham hock filet mignon shoulder hamburger pig venison. Ham bacon corned beef, sausage kielbasa flank tongue pig drumstick capicola swine short loin ham hock kevin.

Bacon ipsum dolor sit amet chuck prosciutto landjaeger ham hock filet mignon shoulder hamburger pig venison. Ham bacon corned beef, sausage kielbasa flank tongue pig drumstick capicola swine short loin ham hock kevin.

You can also add inline spinning wheels. Inline spinners are meant to fit in the flow of the text.

To further indicate progress state you can also append an animated ellipsis at the end of the text using <span class="txtAnimatedEllipsis">.

var inlineSpinner = Wave.Spinner(document.querySelector('.js-inlineSpinner'), {
  inline: true,
  insertionType: 'prepend'
});

inlineSpinner.show();

Inline spinner

You can create spinners manually by inserting their markup into your HTML. To change the color of the spinner, you can use color helpers. By default it will take on the color of the text in it's parent container.


Toggle

Component for hiding, switching or changing the appearence of different contents

Customizable options

var toggle = Wave.Toggle(
  'selector',         // CSS-like trigger selector (String)
  {
    toggleClass: ''   // the class name to be toggled on the target element to switch between different styles
  }
);

Toggles relies on data-target attribute, so be sure to add it to the trigger element. You can use any selector with the target attribute: the component will add or remove the class from the target. Note that the target must be a parent container of the trigger element.

By itself, nothing will happen; But create a CSS animation for the specified class or even something as simple as display: none then watch the component come to life.

Component's API

.open() adds the specified class name to the traget.

.close() removes the specified class name from the target.

.toggle() toggles the specified class name on the target.

.onClickToggle() calls toggle() when the trigger element is clicked.

.destroy() cleans up event listeners.

.on('open', function()) event that triggers when the specified class name is added to the target.

.on('close', function()) event that triggers when the specified class name is removed from the target.

.on('toggle', function()) event that triggers when the specified class name is toggled on the target.

.on('onClickToggle', function()) event that triggers when the trigger element is clicked.

Chain with .overlay() to combine the toggle with the overlay component. The overlay will be appended to the target.

var toggle = Wave.Toggle('.js-triggerName', {
  toggleClass: 'is-shown'
}).overlay({ closable: true });

toggle.open();
toggle.onClickToggle();

See side panel example.


Dropdown

Dropdown menus are used to group secondary or sensitive actions behind a two-step progressive disclosure, while conserving screen real estate.

Usage:

  1. Wrap the dropdown's trigger and the dropdown menu within <div class="dropdown"></div>.
  2. Add class .js-triggerDropdown on the element that should trigger dropdown behavior (usually a button or a link). Remember to include data-target=".dropdown" attribute. data-target is necessary because dropdowns are built with the toggle component.
  3. Wrap the contents of dropdown menu into <div class="dropdown-menu"></div>.

Use .dropdown--right to align dropdown menu to the right:

Example of collapsible navbar with dropdown sub items:

Example of a dropdown menu mixed with a series of buttons:


Collapsible panel

Usage:

  1. Add .collapsiblePanel class to the parent element that holds the trigger element and collapsed content.
  2. Add class .js-triggerExpand on the element that should trigger expand/collapse behavior (usually a button or a link). Remember to include data-target=".collapsiblePanel" attribute. data-target is necessary because collapsible panels are built with the toggle component.
  3. Wrap the contents that should be collapsed into <div class="collapsiblePanel-collapse"></div>.

Adding .collapsiblePanel--is-expanded class to the parent element makes the content expanded by default:

If you need to collapse/expand really long block of content, it is recommended to add .collapsiblePanel--noTransition modifier class. This will result in no height transition effect, but the collapsible content will not be constrained to any explicit max-height.


Side panel

A special panel that slides out from the right or left side of the screen built using the toggle component. Side panels allow the user to focus on the content of the webapp without distractions. They are useful for bringing the features that are important to the user into focus, but only when they are needed.

Wave.Toggle('.js-exampleSidePanel', { toggleClass: 'sideDrawer--is-open' }).onClickToggle();
Show side panel

Side panel content

Close

You can combine side panel with the overlay component to show the mask above the content when panel is opened:

Wave.Toggle('.js-exampleSidePanelWithOverlay', { toggleClass: 'sideDrawer--is-open' }).overlay({ closable: true }).onClickToggle();
Show side panel with overlay

Side panel with overlay content

Close


Drill-down navLimited browser support

Component for creating infinitely nestable navigation

Drill-down nav allows you to organize navigation in lists over multiple levels. Clicking on an item makes the user move deeper into the hierarchy.

This component can be used to create mobile-friendly, complex navigation structures. See multi-level navigation example.

Drill-down nav internally relies on flexbox, which is not supported by older versions of Internet Explorer (IE < 11).

Customizable options

var drillDownNav = Wave.DrillDownNav(
  element,                     // a DOM node to bind the drill-down nav to
  {
    rootSelector: false,       // selector for top-level navigation container (defaults to the `element` argument when not specified)
    subNavToggleSelector: ''   // selector for node that upon clicking will toggle sub navigation it belongs to (defaults to '.drillDownNav-trigger')
  }
);

Component's API

.close() collapses drill-down nav back to the root level.

.openPanel(panel) programatically opens a panel (you can obtain flattened list of panels by accessing panels property on the DrillDownNav instance).

Usage example

var drillDownNav = Wave.DrillDownNav(
  document.querySelector('.js-exampleDrillDownNav')
);

$(document.querySelector('.js-closeDrillDownNav')).on('click', function (e) {
  e.preventDefault();
  drillDownNav.close();
});

$(document.querySelector('.js-openPanelDrillDownNav')).on('click', function (e) {
  e.preventDefault();
  var panelToOpen = drillDownNav.panels.find(panel => panel.el.getAttribute('data-name') === 'nav-header-1');
  if (panelToOpen && panelToOpen.state !== true) {
    drillDownNav.openPanel(panelToOpen);
  }
});

Please follow the markup structure of example below.


Sidebar

Component for creating a mobile-friendly, responsive sidebar

Sidebar shows up if the horizontal space is sufficient, such as on a desktop screen and automatically collapses off-canvas on smaller width devices.

Customizable options

var sidebar = Wave.Sidebar(
  element,            // a DOM node to bind the sidebar to
  {
    staticClass: ''   // custom class(es) for static version (sidebar is always visible)
  }
);

Component's API

.show() programatically slides sidebar into view in overlay mode (mobile layout only).

.close() programatically sends sidebar back off-canvas (mobile layout only).

.destroy() cleans up instance data and detaches all event listeners.

Usage example

Wave.Sidebar(
  document.querySelector('.js-sidebar'),
  {
    staticClass: 'bdrRs bdr--gray20'
  }
);

Please follow the markup structure of example below.

Remember to specify id attribute on the sidebar that toggle button(s) should point to with aria-controls attribute.

You can control at which breakpoint static variation of the sidebar kicks in using .sidebar--static@[breakpoint] classes, i.e. .sidebar--static@large, .sidebar--static@xLarge.

For a more full featured use case, check out the sidebar navigation template.

Main content

Range bar

Component for creating range sliders

Customizable options:

var r = Wave.RangeBar(
  {
    initialRanges: [],                      // array of value pairs; each pair is the min and max of the range it creates
    min: 0,                                 // value at start of bar
    max: 100,                               // value at end of bar
    valueFormat: function(v) {return v;},   // format a value on the bar for output
    valueParse: function(v) {return v;},    // parses an output value for the bar
    step: 0,                                // clamps range ends to multiples of this value (in bar units)
    minSize: 0,                             // smallest allowed range (in bar units)
    bgMarks: 0,                             // number of marks/cells to paint in the background of the bar
    maxRanges: Infinity,                    // maximum number of ranges
    readonly: false,                        // if true, ranges can't be changed with the UI, but can be changed with the api
    label: function(v) {return v;}          // function that transforms range value to text inside the label below the bar
                                            // or plain string
  }
);

Component's API

.add(value, options) adds a new range, options is optional and let's you pass range color.

.remove(rangeId) removes range specified by rangeId.

.removeAll() removes all ranges.

.val() returns array of pairs of min and max values of range.

.setVal(values) updates the ranges in the bar with the values.

.rangeValue(rangeId) returns a value of range specified by rangeId. If range doesn't exists returns false.

.rangeValue(rangeId, value) sets a value of range specified by rangeId. Returns true if range exists, otherwise false.

.data() returns list with data of all ranges.

.on('changing' function(data))
Event that triggers constantly as the value changes. Useful for reactively triggering things in the UI. Callback is passed an object with data of all the ranges and the range that is changing.

.on('change' function(data))
Event that triggers after the user has finished changing a range. Callback is passed an object with data of all the ranges and the range that has changed.

.on('add' function(data))
Event that triggers when range is added. Callback is passed an object with data of all the ranges and the range that is added.

.on('remove' function(data))
Event that triggers when range is removed. Callback is passed an object with data of all the ranges and the range that is removed.

Usage example

var r1 = Wave.RangeBar({
  min: 0,
  max: 10,
  step: 1,
  bgMarks: 10
});
$(document.querySelector('.js-rangeBar1')).append(r1.el);
r1.on('change', function (data) {
  console.log('[Wave.RangeBar] changed: ' + JSON.stringify(data, null, 4));
});
r1.add([3, 6]);

$(document.querySelector('.js-updateRange1')).on('click', function (e) {
  console.log('[Wave.RangeBar] original val: ' + JSON.stringify(r1.val(), null, 4));
  r1.removeAll();
  r1.add([2, 8], { color: 'success--strong' });
  console.log('[Wave.RangeBar] updated val: ' + JSON.stringify(r1.val(), null, 4));
  e.preventDefault();
});

See console log for the output.

Example below demonstrates usage of valueParse and valueFormat functions when operating with non-integer values:

This example uses Day.js library for manipulating dates.

var r2 = Wave.RangeBar({
  min: dayjs().startOf('day').format(),
  max: dayjs().startOf('day').add(1, 'day').format(),
  step: 1000 * 60 * 15,
  minSize: 1000 * 60 * 60,
  initialRanges: [
    [
      dayjs().startOf('day').format(),
      dayjs().startOf('day').add(7.5, 'hours').format()
    ]
  ],
  valueFormat: function (value) {
    return dayjs(value).format();
  },
  valueParse: function (date) {
    return dayjs(date).valueOf();
  },
  label: function (minMaxTuple) {
    var minDate = dayjs(minMaxTuple[0]);
    var maxDate = dayjs(minMaxTuple[1]);

    return maxDate.diff(minDate, 'hours') + ' hours';
  }
});
$(document.querySelector('.js-rangeBar2')).append(r2.el);

var getTotalHours = function getTotalHours(data) {
  var result = 0;
  for (var i = 0; i < data.length; i++) {
    var range = data[i];
    result += dayjs(range.val[1]).diff(dayjs(range.val[0]), 'hours');
  }
  return result;
};
var r2ResultChange = document.querySelector('.js-rangeBar2ResultChange');
var r2ResultChanging = document.querySelector('.js-rangeBar2ResultChanging');
r2.on('change', function (eventData) {
  r2ResultChange.innerHTML = String(getTotalHours(eventData.data));
});
r2.on('changing', function (eventData) {
  r2ResultChanging.innerHTML = String(getTotalHours(eventData.data));
});
  • Hours while changing: start dragging
  • Hours after change: start dragging

Example below shows how to generate a read-only, static bar with multiple ranges:

var r3 = Wave.RangeBar({
  readonly: true, // whether the bar should be read-only
  initialRanges: [
    {
      value: [14, 20],
      color: 'info--strong' // set different color for each range
    },
    {
      value: [10, 14],
      color: 'warning--strong'
    },
    {
      value: [6, 10],
      color: 'danger--strong'
    }
  ],
  barClass: 'rangeBar--small', // any additional class to append to the range bar
  min: 0,
  max: 26,
  step: 1,
  bgMarks: 26
});
$(document.querySelector('.js-rangeBar3')).append(r3.el);

Progress

Progress bar component for showing the user a visualization of some percentage

Customizable options

var progressBar = Wave.ProgressBar(
  element,                                                  // a DOM node to which to append the progress bar
  {
    progressBarClass: 'fill-neutral progressBar--rounded',  // any additional class to append to the progress bar
    color: 'success--strong',                               // initial color of the progress line ("info--strong" is default)
    caption: '',                                            // initial caption to be displayed below the progress line
    htmlCaption: false,                                     // whether the caption should be written as html (defaulting to text)
    captionClass: ''                                        // any additional class to append to the caption
  }
);

Component's API

.setProgress(number) sets the progress bar completion percentage. Should be numeric value between 0 and 100.

.getProgress() gets the current progress bar completion percentage.

.setFill(color) sets the background fill color of the progress line.

.setCaption(caption) sets the caption to be displayed below the progress line.

.status(status) sets the status of the progress bar (for example "error").

.hide() hides the progress bar.

.show() shows the progress bar.

.on('update', function(progress)) event that triggers when the completion percentage is updated.

.on('status', function(status)) event that triggers when the status is changed.

Usage example

// Create progress bar
var progressBar = Wave.ProgressBar(document.querySelector('.js-progressBar'), {
  progressBarClass: 'fill-neutral',
  color: 'accent--strong'
});

progressBar.on('progress', function (progress) {
  console.log('[ProgressBar] progress: ' + progress + '%');
});

var progress = 0,
  progressInterval = null;

$(document.querySelector('.js-simulateProgress')).on('click', function (e) {
  e.preventDefault();

  if (progressInterval !== null) {
    clearInterval(progressInterval);
    progressInterval = null;

    // We've encountered an error
    progressBar.status('error');
  } else {
    // Set back the progress value and show the progress bar
    progressBar.setCaption(progress + '%').setProgress(progress).show();

    progressInterval = setInterval(function () {
      progress += Math.floor(Math.random() * 20);

      // Progress bar is running and moving to completion
      progressBar.status('is-running').setCaption(progress + '%').setProgress(Math.min(progress, 100));

      if (progress >= 100) {
        // Progress bar is completed
        progressBar.status('is-completed');

        clearInterval(progressInterval);
        progressInterval = null;
        progress = 0;

        if ($(document.querySelector('.js-hideProgress')).prop('checked')) {
          // Hide the progress bar
          progressBar.hide();
        }
      }
    }, 350);
  }
});

Simulate loading progress

You can also create static progress bars in the markup. .progressBar--rounded modifier class applies rounded corners to the progress bar.

Progress 30%


Select field

A wrapper around native select field providing consistent look across browsers. Select fields allow the user to select a value from a list of options. On mobile devices they are tightly-coupled with the OS itself, which ensures more tailored, native-like user experience. For example, Android often has a radio-button list popup, whereas iOS has a custom scroller covering the bottom half of the window.

Use .selectField--is-disabled along with disabled attribute to apply appropriate "disabled" appearance:


Multi-select field

A dropdown field with selected items styled as list of labels.

Customizable options

var multiSelect = Wave.MultiSelect(
  element,                          // a DOM node to bind the multiselect to
  {
    itemsList: [item1, item2, ...], // values available for selection. Can be simple string or object (only support for one-level deep)
    selectedList: [],               // values selected on initialization (empty array is default)
    inputId: 'multiSelectId',       // id for internal <input> field. Set this to same as for-reference on <label> element you use for this control.
    labelColor: 'accent',           // color for labels in the selected list (accent is default)
    labelOutline: false,            // specify if labels in the selected list should be outlined instead of filled (false is default)
    itemIsObject: false,            // specify if items are objects instead of simple string (automatically set to true if any items in itemsList is typeof object instead of string)
    uniqueIdProperty: 'id',         // property used as unique ID (key) when items are objects. If not specified, a combination of all the item properties is used as key, which may result in performance issues if list has many items with many properties.
    labelProperty: 'name',          // field used for display when items are objects
    labelTemplate: 'Name: {name}',  // template used for display when items are objects, and more complex that just one labelProperty. {} is used as formatter for property name the object.
    emptyStateText: 'All options were selected',            // text to display when all available items are selected
    filteredStateText: 'Some options are hidden by filter', // text to display if some items are hidden by filter
    removeSelectedItemText: 'Remove {label}',               // tooltip text template for remove button shown on each selected item. {label} is used as placeholder for label text.
    removeAllText: 'Remove all items from selection',       // tooltip text on the button for unselecting all selected items that will remove all items from selection
    groupBy: 'groupProperty',       // field to be used as grouping
    ungroupedHeader: 'Ungrouped',   // Group header to be used for all items that does not have any value in groupBy field
    groups: [{                      // array with groups instead of specifying groups with itemsList/groupBy field
      label: 'Group 1',             // display name for group header
      items: [item1, item2, ...],   // values available in this group
    }, ...],
    groupDefaultCollapsed: false,   // set to true if groups should be collapsed as default
    hideGroupCheckbox: false,       // set to true if checkbox should not be shown for group header
    hideGroupCollapseButton: false, // set to true if groups should not be collapsable/expandable. This will override groupDefaultCollapsed to always be false.
    horizontalScroll: false         // set to true if selected elements should not wrap, but scroll horizontally instead
  }
);

If you have a <label> element that should activate the dropdown on click, you need to specify the inputId option to same value as the for atttribute on the <label> element. This will set the id for the internal <input> element that is used as a focus element for the MultiSelect control, in addition to being the search field for filtering values. Alternatively, you can set the for atttribute on the <label> element to the id atttribute on the initial DOM node. If this is done, the for atttribute on the <label> element will be automatically changed to the id for the internal text box inside the multiselect that will handle focus.

Do not put the control inside a label element, because it contains several buttons (for removing items and toggle dropdown list). Doing so will cause strange behaviour, as the buttons will act as triggers for the label.

Label color can be specified with the option labelColor and can be set to a valid Wave color helper class. It can also be set to just accent, which will translate to fill-accent--strong for filled labels, and fg-accent--moderate for outlined labels. Set labelOutline: true to make the label outlined instead of the default filled.

All the label options can be overridden per item by having an object property on the item object named _multiSelect with the label options present to override the main options.

itemsList: [
  {
    id: 'mrHyde',
    title: 'Dr.',
    firstName: 'Henry',
    lastName: 'Jekyll',
    _multiSelect: {
      labelColor: 'info--strong',
      labelOutline: false,
      labelTemplate: '{title} {lastName}'
    }
  }
]

Component's API

.getSelected() gets array of the selected items that are currently in the selected labels list.

.getUnselected() gets array of the unselected items that are currently in the dropdown list.

.setSelected(list) sets new selected items list that is shown in the selected labels list.

.setUnselected(list) sets new unselected items list that is shown in the dropdown list.

.unselectItem(item) moves item from selected list to unselected.

.unselectAll() moves all items from selected list to unselected.

.on('select', function(item)) event that triggers when an item is selected and moved to the .

.on('unselect', function(item)) event that triggers when the switch value changes.

Usage example

var multiSelectSimple = Wave.MultiSelect(
  document.getElementById('multiSelectSimple'),
  {
    itemsList: ['John', 'Jane', 'Michael', 'Sarah-Jane', 'Billy-Joe', 'Patricia', 'Robert', 'Jennifer', 'Billy', 'William', 'Linda', 'David', 'Elizabeth', 'Richard'],
    selectedList: ['Mary']
  }
);
multiSelectSimple.on('select', function (item) {
  console.log('multiSelectSimple on select', item);
});
multiSelectSimple.on('unselect', function (item) {
  console.log('multiSelectSimple on unselect', item);
});

var multiSelectLabel = Wave.MultiSelect(
  document.querySelector('.js-multiSelectLabel'),
  {
    itemsList: [{id: 'mrDoe', name: 'John Doe' }, {id: 'mrsDoe', name: 'Jane Doe' }, {id:'jacquie', name: 'Jacqueline Madelina Vandroogenbroeck'}],
    selectedList: [{id: 'missSmith', name: 'Mary Smith' }],
    inputId: 'multiSelectLabel',
    labelColor: 'success--strong',
    labelProperty: 'name'
  }
);

var multiSelectComplex = Wave.MultiSelect(
  document.querySelector('.js-multiSelectComplex'),
  {
    itemsList: [{id: 'mrDoe', title: 'Mr.', firstName: 'John', lastName: 'Doe' }, {id: 'mrsDoe', title: 'Mrs.', firstName: 'Jane', lastName: 'Doe' }, {id: 'missSmith', title: 'Miss', firstName: 'Mary', lastName: 'Smith' }, { id: 'mrHyde', title: 'Dr.', firstName: 'Henry', lastName: 'Jekyll', _multiSelect: {labelColor: 'success--strong', labelOutline: false, labelTemplate: '{title} {lastName}'} }, {id: 'jacquie', title: 'de', firstName: 'Jacqueline Madelina', lastName: 'Volkenstrasse Vandroogenbroeck' }],
    selectedList: ['missSmith', 'mrDoe'],
    inputId: 'multiSelectComplex',
    labelOutline: true,
    labelColor: 'fg-neutral',
    labelTemplate: '<small>{title}</small> {lastName}, {firstName}',
    uniqueIdProperty: 'id',
    removeAllText: 'Remove all names from selection',
    emptyStateText: 'All names were selected',
    filteredStateText: 'Some names are filtered out based on what you write',
    horizontalScroll: true
  }
);
console.log('multiSelectComplex.getSelected()', multiSelectComplex.getSelected());
console.log('multiSelectComplex.getUnselected()', multiSelectComplex.getUnselected());

var list = multiSelectComplex.getUnselected();
list.unshift({id: 'mrNew', title: 'Mr.', firstName: 'Carl', lastName: 'New' })

multiSelectComplex.setUnselected(list);

First example is simple multi-select where each item is text only.

In next example, each item is object with 2 properties where one property is specified as label.

In next example, each item is object with multiple properties where id is configured, and also a label template that defines how the properties should be shown. In this example, all available items are defined in the itemsList array, and only id's for selected items are specified in the selectedList array. Also horizontal scroll option is present.

Grouped in list

In next 2 examples, items are grouped. First where simple text items are only defined in a groups array. Next is items defined like earlier examples as objects in itemsList array, but with property specified in groupedBy that should act as grouping.

When using groups, the selected items are not removed from dropdown list. There are checkboxes that indicate which are selected in addition to the item is added as label in the selected box.

Usage example

var multiSelectGroupSimple = Wave.MultiSelect(
  document.querySelector('.js-multiSelectGroupSimple'),
  {
    groups: [
      { labelColor: 'info--strong', label: 'Male', items: ['John', 'Michael', 'Billy-Joe', 'Robert', 'Billy', 'William', 'David', 'Richard']},
      { labelColor: 'fg-danger--moderate', labelOutline: true, label: 'Female', items: ['Mary', 'Jane', 'Sarah-Jane', 'Patricia', 'Jennifer', 'Linda', 'Elizabeth']}
    ],
    selectedList: ['Mary'],
    groupDefaultCollapsed: true,
    inputId: 'multiSelectGroupSimple'
  }
);

var multiSelectGroupObjects = Wave.MultiSelect(
  document.querySelector('.js-multiSelectGroupObjects'),
  {
    itemsList: [{id: 'mrDoe', name: 'John Doe', job: 'Teacher'}, {id: 'mrsDoe', name: 'Jane Doe'}, {id: 'missSmith', name: 'Mary Smith', job: 'Teacher'}, {id:'jacquie', name: 'Jacqueline Madelina Vandroogenbroeck', job: 'Athlete'}, { id: 'mrHyde', job: 'Scientist', name: 'Jekyll', _multiSelect: {labelColor: 'success--strong', labelOutline: false, labelTemplate: 'Dr. {name}'} }],
    groupBy: 'job',
    ungroupedHeader: 'Unemployed',
    inputId: 'multiSelectGroupObjects',
    labelProperty: 'name',
    uniqueIdProperty: 'id',
    filteredStateText: 'Some names are hidden by filter',
    hideGroupCheckbox: true,
    hideGroupCollapseButton: true
}
);
            

Toggle switch

Use Toggle switch if you need to represent the switching between two mutually exclusive states or on-off state. The difference between this component and regular checkbox is that Toggle switch should result in an immediate action. Use it for binary settings when changes take effect right after the user flips the toggle. On the contrary, when the user has to perform some extra steps (like cliking a "Submit" button) to apply changes, use a regular checkbox.

Customizable options

var toggleSwitch = Wave.ToggleSwitch(
  element,               // a DOM node to bind the toggle switch to
  {
    value: true,         // initial value of the toggle switch (false is default)
    isDisabled: false,   // whether the toggle switch is initially disabled (false is default)
  }
);

Component's API

.activate() sets switch ON.

.deactivate() sets switch OFF.

.toggle() toggles switch.

.getValue() gets the value of the switch (true or false).

.disable() disables changing the state of the switch.

.enable() enables changing the state of the switch.

.on('change', function(value)) event that triggers when the switch value changes.

Usage example

Wave.ToggleSwitch(
  document.querySelector('.js-toggleSwitchEnabledAndChecked'),
  { value: true }
);

Wave.ToggleSwitch(document.querySelector('.js-toggleSwitchEnabledAndUnchecked'));

Wave.ToggleSwitch(
  document.querySelector('.js-toggleSwitchDisabledAndChecked'),
  {
    value: true,
    isDisabled: true
  }
);

Wave.ToggleSwitch(
  document.querySelector('.js-toggleSwitchDisabledAndUnchecked')
).disable();
On Off
On Off
On Off
On Off

Do not replace the "On" or "Off" labels unless there are other labels (no longer than three of four characters) that are more specific for the setting.

Show Hide

File input

Component for presenting custom interface for opening the file picker

Customizable options

var fileInput = Wave.FileInput(
  element,                                       // a DOM (input[type="file"]) node to create an instance of fileInput
  {
    labelTxt: '{number} filer valgt',            // the text that will show up in the label when multiple files are selected. {number} is a placeholder that will get replaced with the number of selected files
    onChange: function(fileInput, fileList) {}   // callback function to trigger when user selects new file(s). Receives instance of fileInput as the first argument and the FileList object as the second
  }
);

Component's API

.on('changed', function(files)) receives the onchange DOM event of the element. FileList object representing the files selected by the user is passed as the parameter. See FileList for more details.

.enable() enables file input.

.disable() disables file input.

.destroy() reverts the file input back to its original state and removes all event listeners.

Things to keep in mind:

  • Internet Explorer 9 does not support HTML5 file API and thus the ability to select multiple files for a single input. The list will contain only one file object with a name property.

Usage example

var fileInput = Wave.FileInput(document.querySelector('.js-fileInput'), {
  onChange: function (fileInput, fileList) {
    var output = [];
    for (var i = 0, file; file = fileList[i]; i++) {
      output.push('<li><b>', escape(file.name), '</b> (', file.type || 'n/a', ')</li>');
    });
    document.querySelector('.js-fileListOutput').innerHTML = '<ul>' + output.join('') + '</ul>';
  }
});

fileInput.on('changed', function (fileList) {
  console.log(fileList);
});

Toggle code examples to view the required HTML code around the file input.


Number stepper

Component for cross-browser number inputs

Number stepper is based upon the native <input type="number"> so it will automatically detect the minimum, maximum and increment values based on the min, max and step attributes. You can also use keyboard arrow keys to increment and decrement the value.

Customizable options

var numberStepper = Wave.NumberStepper(
  el,                  // a DOM node (input[type="number"]) to bind the stepper to
  {
    stepperClass: ''   // any additional class to append to the input wrapper
    delay: 200         // delay of the stepper's value update in ms when the mouse is kept pressed on up/down button (default is 150)
  }
);

Component's API

.enable() enables number stepper.

.disable() disables number stepper.

.destroy() reverts the input element back to its original state and removes all event listeners.

Usage example

var numberStepper = Wave.NumberStepper(document.querySelector('.js-numberStepper'), {
  stepperClass: 'w20'
});

$(document.querySelector('.js-toggleNumberStepperState')).on('click', function (e) {
  e.preventDefault();
  return (this.state = !this.state) ? numberStepper.disable() : numberStepper.enable();
});

Autosize textarea

Automatically adjust textarea height based on user input

var textareaAutosize = Wave.TextareaAutosize(
  element   // a DOM node (textarea) to autosize as text is entered
);

Usage example

$(document.querySelectorAll('.js-autoSize')).each(function(i, el) {
  Wave.TextareaAutosize(el);
});

Mobile input

Convert regular text input fields to tap-optimized mobile input types on touch devices

Mobile input types can help improve the experience and accessibility. See a short overview of mobile input types

Configurable options

var mobileInput = Wave.MobileInput(
  element,         // a DOM (input) node
  {
    type: 'date'   // specifies which input type should be used (e.g. "date" or "number")
  }
);

Component's API

.isConverted() returns a boolean whether the input element is converted to mobile input type.

.destroy() reverts the input element back to its original state.

Things to keep in mind when validating:

  • After converting to <input type="date"> the field value is always represented as ISO-8601 date format (yyyy-MM-dd) regardless of the presentation format and pattern attribute. Submit the form below to see the returned values.

Usage example

$(document.querySelectorAll('.js-nativeTime')).each(function(i, el) {
  Wave.MobileInput(el, {
    type: 'time'
  });
});

$(document.querySelectorAll('.js-nativeDate')).each(function(i, el) {
  Wave.MobileInput(el, {
    type: 'date'
  });
});

Calendar

Calendar component allows the user to choose a date

Configurable options

var calendar = Wave.Calendar({
  date: new Date(),   // date to be shown (native Date object), defaulting to now
  i18n: {             // language configuration object
    months: [],       // array containing labels for each month
    days: []          // array containing labels for each day
  },
  firstDay: 1         // first day of the week, it can be 1 if week starts from Monday or 0 if from Sunday and so on (defaulting to 1)
});

Component's API

.select(date) selects the given date (native Date object).

.show(date) shows the given date (native Date object). This does not select the given date, it simply ensures that the date is visible in the current view.

.prev() shows the previous month.

.next() shows the next month.

.min(date) defines earliest valid date (inclusive).

.max(date) defines latest valid date (inclusive).

.showMonthSelect() adds month selection input.

.showYearSelect([from], [to]) adds year selection input, with optional range specified by from and to, which defaults to the current year -10 / +10.

.on('view change', function(date, action))
Event that triggers when the viewed month/year is changed without modification of the selected date. Callback is passed the Date object and action that caused the view to change ("prev", "next", "month" or "year").

.on('change', function(date))
Event that triggers when the selected date is modified. Callback is passed the Date object.

Usage example

var calendarOne = Wave.Calendar();
calendarOne.on('change', function (date) {
  console.log('[Wave.Calendar] selected: %s of %s %s',
    date.getDate(),
    date.getMonth(),
    date.getFullYear()
  );
});
calendarOne.on('view change', function (date, action) {
  console.log('[Wave.Calendar] change %s', action);
});
calendarOne.addClass('pAs bdrAs bdr--gray20');
document.querySelector('.js-calendarOneContainer').appendChild(calendarOne.el);

var calendarTwo = Wave.Calendar({
  i18n: {
    months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
    days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag']
  }
})
  .showMonthSelect()
  .showYearSelect()
  .min(new Date(2008, 5, 12))
  .max(new Date(2008, 7, 19))
  .select(new Date(2008, 7, 19));

calendarTwo.addClass('pAs fill-neutral--minimal');
document.querySelector('.js-calendarTwoContainer').appendChild(calendarTwo.el);

Date picker

Date picker built on calendar component

Configurable options

var datePicker = Wave.DatePicker(
  element,                                        // a DOM (input) node
  {
    format: 'dd.mm.yyyy',                         // format of the date you expect datepicker to produce, defaulting to "YYYY-MM-DD"
    i18n: {                                       // language configuration object
      months: [],                                 // array containing labels for each month
      days: []                                    // array containing labels for each day
    },
    firstDay: 1                                   // first day of the week, it can be 1 if week starts from Monday or 0 if from Sunday and so on (defaulting to 1)
    useNativePickerOnTouchCapableDevices: true,   // whether to switch to native picker on touch capable devices (true by default)
    triggerChangeOnEmpty: false,                  // whether to emit change event with `null` value when date picker's input is emptied (false by default)
    popoverClass: ''                              // any additional class to append to date picker's popover
    popoverNode: HTMLElement                      // a DOM node to which the date picker's popover will be appended (defaulting to document.body)
  }
);

When useNativePickerOnTouchCapableDevices option is enabled, Date picker uses Mobile Input internally to switch to native date picker control on touch devices that support it. API methods below like .show(), .hide() or .position() will have no effect with the native control. Use .isNative() method to detect whether the native control has actually been invoked.

Component's API

.value(date) gets or sets current value. Value is being set and retrieved as native Date object. date argument is optional.

.show() makes the date picker visible.

.hide() hides the date picker.

.position(position) sets the position of the popover relative to the element, i.e. "top"|"left"|"right"|"bottom right"|"top left" ("bottom" is default).

.isNative() returns a boolean specifying whether the native mobile date picker is invoked on touch devices.

.min(minDate) defines minimum selectable date via picker. minDate argument accepts a Date object or date string that is parsable.

.max(maxDate) defines maximum selectable date via picker. maxDate argument accepts a Date object or date string that is parsable.

.on('change', function(date)) event that triggers when the date is changed. Callback is passed the Date object.

.on('show', function()) event that triggers when the date picker becomes visible.

.on('hide', function()) event that triggers when the date picker becomes hidden.

Usage example

var datePicker = Wave.DatePicker(document.querySelector('.js-datePicker'), {
  format: 'dd.mm.yyyy',
  i18n: {
    months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
    days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag']
  }
});
console.log(datePicker.value()); // => currently selected date as a Date instance

datePicker.on('change', function (date) {
  console.log('[Wave.DatePicker] new date: %d of %d %d',
    date.getDate(),
    date.getMonth(),
    date.getFullYear()
  );
});
Must be a valid date in format dd.mm.yyyy

Time picker

Component for time selection

Configurable options

var timePicker = Wave.TimePicker(
  element,                                        // a DOM (input) node
  {
    i18n: {                                       // language configuration object
      hours: '',                                  // label for the hours (String)
      minutes: ''                                 // label for the minutes (String)
    },
    minuteStep: 5                                 // the amount of time, in minutes, between each minute item. Can be 5, 10 or 15 (defaults to 1 if not specified)
    useNativePickerOnTouchCapableDevices: true,   // whether to switch to native picker on touch capable devices (true by default)
    popoverClass: ''                              // any additional class to append to time picker's popover
    popoverNode: HTMLElement                      // a DOM node to which the time picker's popover will be appended (defaulting to document.body)
  }
);

When useNativePickerOnTouchCapableDevices option is enabled, Time picker uses Mobile Input internally to switch to native time picker control on touch devices that support it. API methods below like .show(), .hide(), .position(), .min() or .max() will have no effect. Use .isNative() method to detect whether the native control has actually been invoked.

Component's API

.value(time) gets or sets current value. Value is being set and retrieved as { hour, minute } object. time argument is optional.

.show() makes the time picker visible.

.hide() hides the time picker.

.position(position) sets the position of the popover relative to the element, i.e. "top"|"left"|"right"|"bottom right"|"top left" ("bottom" is default).

.min(minTime) defines minimum selectable time (inclusive). minTime argument is an object with { hour, minute } shape.

.max(maxTime) defines maximum selectable time (inclusive). maxTime argument is an object with { hour, minute } shape.

.isNative() returns a boolean specifying whether the native mobile time picker is invoked on touch devices.

.on('change', function(time, complete)) event that triggers when the time is changed. Callback is passed time object with hour and minute properties, complete is true only if both hours and minutes have been clicked by the user.

.on('show', function()) event that triggers when the time picker becomes visible.

.on('hide', function()) event that triggers when the time picker becomes hidden.

Usage example

var timePicker = Wave.TimePicker(document.querySelector('.js-timePicker'), {
  i18n: {
    hours: 'Time',
    minutes: 'Minutt'
  }
});
console.log(timePicker.value()); // => currently selected time ({ hour, minute })

timePicker.on('change', function (time) {
  console.log('[Wave.TimePicker] new time: %d:%d',
    time.hour,
    time.minute
  );
});
Must be a valid time in format hh:mm

Sticky

Component for sticking elements to the top of the viewport when the user scrolls down

Sticky makes elements stick and remain visible as the user scrolls. Elements alternate between being in-flow and having position: fixed as soon as they reach the top of the viewport.

Configurable options

Wave.Sticky(
  element,                                      // a DOM node || a list of DOM nodes
  {
    top: 50,                                    // offset from the top of the window to stick the element (default is 0)
    zIndexBase: 100,                            // base z-index for elements that are stuck (default is 0)
    zIndexManagement: false,                    // whether z-index is changed when stuck/unstuck (default is true)
    stickyClass: 'my-sticky',                   // class name added to elements that are stuck
    placeholderClass: 'my-stickyPlaceholder',   // class name for placeholder elements (placeholders are used to keep the original page height)
    onStuck: function(element) {},              // callback function to trigger when an element is stuck
    onUnstuck: function(element) {}             // callback function to trigger when an element is unstuck
  }
);

Component's API

.update() updates position of the element(s). Attach this method to the scroll event of the window (or another element).

.destroy(element) removes sticky functionality from the given node.

Usage example

var stickyInstance = Wave.Sticky(document.querySelectorAll('.js-sticky'), {
  top: 56,
  zIndexBase: 100,
  onStuck: function (element) {
    console.log('[Wave.Sticky] element is stuck!');
  }
});

$(window).on('scroll', function() {
  stickyInstance.update();
});
Fixed to viewport top A.

Bacon ipsum dolor sit amet chuck prosciutto landjaeger ham hock filet mignon shoulder hamburger pig venison. Ham bacon corned beef, sausage kielbasa flank tongue pig drumstick capicola swine short loin ham hock kevin.

Bacon ipsum dolor sit amet chuck prosciutto landjaeger ham hock filet mignon shoulder hamburger pig venison. Ham bacon corned beef, sausage kielbasa flank tongue pig drumstick capicola swine short loin ham hock kevin.

Fixed to viewport top B.

Image container

Component for filling-in/fitting-in images into containers

This component lets you scale any image to fit in a certain way into a defined container area.

Image container is a cross-browser alternative to the CSS3 image-fit property that is not supported in every browser, namely Internet Explorer.

By default container will fit the image. The image will not be scaled.

You can scale the image proportionally to fill container's width, height, or both width and height:

You can center the image within the container. If the image is larger than the container all sides are cropped to center the image. If, on the other hand, the image is smaller, it is centered in the empty space of the container.

The image can be contained in the container. If the image has a wider aspect ratio (landscape) than the container, it is scaled to fit container's width and the top and bottom are empty. If the image has a taller aspect ratio (portrait) then the container, it is scaled to fit container's height and the sides are empty.

You can also make the image cover the container. If the image has a wider aspect ratio (landscape) than the container, it is scaled to fit container's height and the sides are cropped evenly. If the image has a taller aspect ratio (portrait) then the container, it is scaled to fit container's width and the top and bottom are cropped evenly.


Responsive embed

Component for responsive, intrinsic ratio embeds

Intrinsic ratio is a technique to fluidly constrain a child element to a ratio set in their parent element. The child element will adapt to different screen dimensions, while also maintaining the specified aspect ratio.

This component utilizes the ratio utilites. Use them to set the intrinsic ratio. Default ratio is 1:1.

1:1 ratio
2:1 ratio at large breakpoint
4:3 ratio below large breakpoint
16:9 ratio

You can use this technique to make different types of embedded content (iframes, images etc.) responsive:

map example
Announcement

This is the technical documentation of Wave Core library, the foundational part of Wave Design System.

Currently we are working on brand new design system site and a set of polished React components that aim to work out of the box, be accessible and deliver superior developer and user experience. Browse the new Wave Design System site and Wave React components docs at https://wave.volue.com.