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.


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.


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 page 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


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 ->
    False ->

wmInputWithEvents : WmInputAttributes -> (String -> msg) -> msg -> Html msg
wmInputWithEvents a inputMsg blurMsg =
  node "wm-input"
    [ Attrs.attribute "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"
    , 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" ( message valDecoder)

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

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!

In a compound component like Select or Action Menu, child components are rendered in the parent's slot element, part of its shadow DOM tree. Slots essentially serve as a placeholder for markup that you the developer define in the light DOM tree, like <wm-menuitem>, <wm-option>, text content, or other child elements.

The browser distributes the child elements defined in the light DOM into the shadow DOM of the parent. The result is a flattened DOM tree—a merger of the the light DOM and the shadow DOM. This flattened tree is what you see in DevTools and what is rendered on the page.

With the standard implementation of the component, dynamically updating the child items will throw an error. Elm's efficient diffing of the DOM will register that only the child items have changed and try to update them, but the component has already been composed.

Rendering the component in a Keyed node and giving it a dynamic id will cause the entire component, rather than just the child items, to render anew, avoiding the error.