API design approach - Material UI (original) (raw)

We have learned a great deal regarding how Material UI is used, and the v1 rewrite allowed us to completely rethink the component API.

API design is hard because you can make it seem simple but it's actually deceptively complex, or make it actually simple but seem complex.@sebmarkbage

As Sebastian Markbage pointed out, no abstraction is superior to wrong abstractions. We are providing low-level components to maximize composition capabilities.

Composition

You may have noticed some inconsistency in the API regarding composing components. To provide some transparency, we have been using the following rules when designing the API:

  1. Using the children prop is the idiomatic way to do composition with React.
  2. Sometimes we only need limited child composition, for instance when we don't need to allow child order permutations. In this case, providing explicit props makes the implementation simpler and more performant; for example, the Tab takes an icon and a label prop.
  3. API consistency matters.

Rules

Aside from the above composition trade-off, we enforce the following rules:

Spread

Props supplied to a component which are not explicitly documented are spread to the root element; for instance, the className prop is applied to the root.

Now, let's say you want to disable the ripples on the MenuItem. You can take advantage of the spread behavior:

<MenuItem disableRipple />

The disableRipple prop will flow this way: MenuItem > ListItem > ButtonBase.

Native properties

We avoid documenting native properties supported by the DOM like className.

CSS Classes

All components accept a classes prop to customize the styles. The classes design answers two constraints: to make the classes structure as simple as possible, while sufficient to implement the Material Design guidelines.

const styles = {
  root: {
    color: green[600],
    '&$checked': {
      color: green[500],
    },
  },
  checked: {},
};

Nested components

Nested components inside a component have:

Prop naming

Controlled components

Most controlled components are controlled by the value and the onChange props. The open / onClose / onOpen combination is also used for displaying related state. In the cases where there are more events, the noun comes first, and then the verb—for example: onPageChange, onRowsChange.

boolean vs. enum

There are two options to design the API for the variations of a component: with a boolean; or with an enum. For example, let's take a button that has different types. Each option has its pros and cons:

type Props = {  
  contained: boolean;  
  fab: boolean;  
};  

This API enables the shorthand notation:<Button>, <Button contained />, <Button fab />.

type Props = {  
  variant: 'text' | 'contained' | 'fab';  
};  

This API is more verbose:<Button>, <Button variant="contained">, <Button variant="fab">.
However, it prevents an invalid combination from being used, bounds the number of props exposed, and can easily support new values in the future.

The Material UI components use a combination of the two approaches according to the following rules:

Going back to the previous button example; since it requires 3 possible values, we use an enum.

Ref

The ref is forwarded to the root element. This means that, without changing the rendered root element via the component prop, it is forwarded to the outermost DOM element which the component renders. If you pass a different component via the component prop, the ref will be attached to that component instead.

Glossary