components / Library version 5.3.1

Components Overview

Browse our available UI components to understand the functionality and purpose of each component, and review installation and usage instructions to understand how to integrate them into your projects.

Components Installation Usage

Layout Containers

Components that serve to contain content.

Installation

The component library is distributed as javascript modules (CommonJS and ES modules) to use with a bundler, as well as standalone javascript files that can be loaded directly (e.g. via a script tag). It also includes an ES5 build for older browsers, and we release a separate react-specific package (see instructions at the bottom of this page). The core distribution includes the following builds:

  • dist/cjs CommonJS, for bundlers
  • dist/esm ES modules, for bundlers
  • dist/esm-es5 ES modules, compatible with older browsers
  • dist/collection for lazy loading in other Stencil applications
  • dist/ripple for loading directly, e.g. via a script tag

We recommend that you let Stencil's loader determine which build to use. For this, simply import and execute the defineCustomElements function in dist/loader.

The components use the Material Design Icons, an icon set which is probably already being pulled in your codebase (unless your product has not adopted the Ripple design system yet). If it is not available, make sure to load it before using the components, for instance by adding a link tag in the page head:

<link href="https://cdn.materialdesignicons.com/3.6.95/css/materialdesignicons.min.css" rel="stylesheet">

The components also use Ripple's color tokens, which are loaded separately so you can use them in your projects:

<link href="https://cdn.watermarkinsights.com/css/1.3.0/tokens.css" rel="stylesheet">

Installing in a NodeJS application

  1. Add the package:

    npm i @watermarkinsights/ripple@5.3.1
  2. Import the loader and polyfill functions:

    import { applyPolyfills, defineCustomElements } from '@watermarkinsights/ripple/dist/loader';
  3. Call them to load the components:

    applyPolyfills().then(() => {
      defineCustomElements();
    });
    

You can now use the components as you would any other html element: <wm-button>Click me</wm-button>

To update, simply run the install command again, specifying the desired version.

Loading as a script

Alternatively, you can simply load this script to use the library:

<script type="module" src="https://cdn.jsdelivr.net/npm/@watermarkinsights/ripple@5.3.1/dist/ripple/ripple.js"></script>

To update, replace 5.3.1 by the desired version.

Installing React Wrappers

If your project uses React, install this package instead of the core library to use the Ripple component the React way. The components' names and props will be CamelCased rather than hyphenated, and you will be able to hook to events directly on the component (e.g. onWmEventTriggered=...), rather than .addEventListener("wmEventTriggered", ...).

  1. Add the package:

    npm i @watermarkinsights/ripple-react@5.3.1
  2. Import the components at the top of your file:

    import { WmButton, WmSelect, WmOption } from '@watermarkinsights/ripple-react';

As for the core library, the Material Design Icons and Ripple's color tokens need to be loaded before.

To update, simply run the install command again, specifying the desired version.

Usage

Issue reporting

Please follow the instructons in the Components Issues Checklist when reporting an issue.

Basic usage

The components API (properties and events) is documented on this site. Please do not use undocumented props or events. Some of them are for the components' internal use and may change at any time, breaking functionality. Please contact the Design System Team if you think the current API doesn't allow a particular scenario. We'll either adapt the component or show you a different way to handle your case.

Components width

Many components have a width of 100%, meaning they will take the entire width of their parent. You can control the width by setting the width of the parent element.

Font size

The font size of the components is inherited from the page. If they display at a wrong size, check if a font size was set on the body. For accessibility reasons, you should not set a fixed global size. Browers default this value to 16px, and users have the ability to change this value in the settings. Setting a fixed value on the body can interfere with this, since the page won't respond to the user's preference.

Re-rendering

Tip: If you are adding or removing child nodes in a component, make sure to re-render the parent component.

Front-end frameworks like React or Elm take care of re-rendering elements when they change. You can dynamically change a component the way you would any other element and expect the changes to be reflected.

But things are a bit different with compound components (when components have a parent-child relationship). If you dynamically change a property on a child component, there may not be a visible change to its parent or vice versa. For instance changing the selected property of an Option will not update the Select component, which will keep displaying the previously displayed item. Similarly, changing the selected-tab property on wm-tab-list will not re-render its children wm-tab-item.

A common solution to this problem is to use keyed nodes, which will cause a change in one component to trigger a re-render in the other.

The Shadow DOM

The components are rendered in the shadow DOM, which isolates them from the rest of the DOM.
A few things to note:

  1. Styles don't bleed in.

    The shadow host element can be selected, but nothing in the shadow DOM will be affected by external styles.
    Targeting the host element can be useful for positioning the component on the page.
    Components rendered as children of other components (e.g., a wm-option in a wm-action-menu) can also be selected.

    In other words, you can style what you write the markup for, but not what is built into the component.

  2. Inheritable styles continue to inherit.

    body {
    color: purple; /* will apply inside the components */
    }
    

  3. Elements in the shadow root can be queried via the Element.shadowRoot property.

    You shouldn't have to reach in a component's shadow DOM. If you do, please let us know your use case so we can adapt the component, or show you the intended use in your case.

Flash of Unstyled Content

Unstyled component content can sometimes appear on the page before the component is loaded, because the browser doesn't know the custom element yet and treats it as a div. You can add a rule to your app's styles to ensure content is hidden before the component is loaded. Here's an example for wm-button:
wm-button:not(.hydrated) { visibility: hidden }
Add a rule per component, as needed.

Accessibility

The components are already fully accessible. Accessibility doesn't need to be implemented.

Usage in React

The library can be used as React components. The web components are simply wrapped in a React component, allowing usage of the library as any other React component. In particular, events become available the React way (e.g. onWmEventTriggered). Please refer to the installation tab for instructions.

Usage in Elm

We recommend creating helper functions to simplify the use of components. In Phoenix-based projects, you should find some in assets/elm/Common/Components.elm. Feel free to use and update this file. This file is not maintained by the Ripple team.

Simple helper

To render a Ripple component in an Elm app, you need to use the node function, passing it the name of the node to render, e.g. node "wm-button", followed by the list of attributes and the list of children, like any other HTML element. In its simplest form, a helper can simply abstract away this node "wm-componentname" part. Here's an example:

-- imports for all the examples on this page
import Html exposing (Html, div, text, button, node)
import Html.Events as Events
import Html.Attributes as Attrs
import Json.Decode as JD

wmButton : List (Html.Attribute msg) -> List (Html msg) -> Html msg
wmButton buttonAttrs buttonText = node "wm-button" buttonAttrs buttonText

Presets

But there is much more to it. Components are often reused almost identically, most attributes being always set to the same values. You can create a "preset" to abstract away the repeated parts. For instance here's a function that renders a primary button. It takes the button text and the onclick function as arguments and does the rest for you:

primaryButton : String -> msg -> Html msg
primaryButton buttonText onclickFunc =
node "wm-button"
  [ Attrs.attribute "button-type" "primary", Events.onClick onclickFunc ]
  [ text buttonText ]

If you often reach for an icononly button with the "close" icon, you could create a helper with all attributes already set. Please note that the string "close" is not internationalized in this example:

closeButton : msg -> Html msg
closeButton onClickFunc =
node "wm-button"
  [ Attrs.attribute "button-type" "icononly"
  , Attrs.attribute "icon" "f156"
  , Attrs.attribute "tooltip" "close"
  , Events.onClick onClickFunc
  ]
  [ text "Close" ]

Using types

To pass many attributes, you may want to represent the attributes as a record and declare a type alias:

type alias WmInputAttributes =
{ id : String
, label : String
, labelPosition : String
, requiredField : Bool
, placeholder : String
, info : String
, value : String
, characterLimit : Int
, disabled : Bool
, errorMessage : String
}

stringFromBool : Bool -> String
stringFromBool b =
case b of
  True ->
      "true"
  False ->
      "false"

wmInputWithEvents : WmInputAttributes -> (String -> msg) -> msg -> Html msg
wmInputWithEvents a inputMsg blurMsg =
node "wm-input"
  [ Attrs.attribute "id" a.id
  , Attrs.attribute "label" a.label
  , Attrs.attribute "label-position" a.labelPosition
  , Attrs.attribute "required-field" (stringFromBool a.requiredField)
  , Attrs.attribute "placeholder" a.placeholder
  , Attrs.attribute "info" a.info
  , Attrs.value a.value
  , Attrs.attribute "character-limit" (String.fromInt a.characterLimit)
  , Attrs.attribute "error-message" a.errorMessage
  , Attrs.attribute "disabled" (stringFromBool a.disabled)
  , Events.onInput inputMsg
  , Events.onBlur blurMsg
  ]
  []

Using custom events

Here is a way to hook up a custom event:

onPageClicked : (Int -> msg) -> Html.Attribute msg
onPageClicked message = Events.on "wmPaginationPageClicked" (JD.map message valDecoder)


valDecoder : JD.Decoder Int
valDecoder = JD.field "target" (JD.field "value" JD.int)


wmPagination : Int -> Int -> Int -> (Int -> msg) -> Html msg
wmPagination currentPage totalItems itemsPerPage updatePage =
node "wm-pagination"
  [ Attrs.attribute "current-page" (String.fromInt currentPage)
  , Attrs.attribute "total-items" (String.fromInt totalItems)
  , Attrs.attribute "items-per-page" (String.fromInt itemsPerPage)
  , onPageClicked updatePage
  ]
  []

The valDecoder function above is needed to extract a string, but sometimes all we need is to know whether the event has fired. For instance whether a button was clicked. Here's a simpler event handler for these cases. Note: Modal emits the event wmModalCloseTriggered when the close button is pressed:

onWmModalCloseTriggered : msg -> Html.Attribute msg
onWmModalCloseTriggered message = Events.on "wmModalCloseTriggered" (JD.succeed message)

Don't hesitate to reach out to the Ripple team if you need help writing helpers!

Neutral Theme

For Faculty Success we provide an alternate "Neutral" theme for our component library. To enable this theme, include the following attribute on your <html> element: wm-theme="neutral"

Back to Top