Theming

Whether you need to adjust a CSS rule for a single component, or change the color of the labels in the entire app, you’re covered!

Overriding A Component Style

Most admin-on-rest components support two style props to set inline styles:

  • style: A style object to customize the look and feel of the component container (e.g. the <td> in a datagrid). Most of the time, that’s where you’ll want to put your custom styles.
  • elStyle: A style object to customize the look and feel of the component element itself, usually a material ui component. Use this prop when you want to fine tune the display of a material ui component, according to their styling documentation.

These props accept a style object:

import { EmailField } from 'admin-on-rest/mui';

<EmailField source="email" style={{ backgroundColor: 'lightgrey' }} elStyle={{ textDecoration: 'none' }} />
// renders in the datagrid as
<td style="background-color:lightgrey">
    <a style="text-decoration:none" href="mailto:[email protected]">
        [email protected]
    </a>
</td>

Some components support additional props to style their own elements. For instance, when using a <Datagrid>, you can specify how a <Field> renders headers with the headerStyle prop. Here is how to make a column right aligned:

export const ProductList = (props) => (
    <List {...props}>
        <Datagrid>
            <TextField source="sku" />
            <TextField
                source="price"
                style={{ textAlign: 'right' }}
                headerStyle={{ textAlign: 'right' }}
            />
            <EditButton />
        </Datagrid>
    </List>
);

Refer to each component documentation for a list of supported style props.

If you need more control over the HTML code, you can also create your own Field and Input components.

Conditional Formatting

Sometimes you want the format to depend on the value. Admin-on-rest doesn’t provide any special way to do it, because React already has all that’s necessary - in particular, Higher-Order Components (HOCs).

For instance, if you want to highlight a <TextField> in red if the value is higher than 100, just wrap the field into a HOC:

const colored = WrappedComponent => props => props.record[props.source] > 100 ?
    <span style={{ color: 'red' }}><WrappedComponent {...props} /></span> :
    <WrappedComponent {...props} />;

const ColoredTextField = colored(TextField);

export const PostList = (props) => (
    <List {...props}>
        <Datagrid>
            <TextField source="id" />
            ...
            <ColoredTextField source="nb_views" />
            <EditButton />
        </Datagrid>
    </List>
);

If you want to read more about higher-order components, check out this SitePoint tutorial: Higher Order Components: A React Application Design Pattern

Responsive Utility

To provide an optimized experience on mobile, tablet, and desktop devices, you often need to display different components depending on the screen size. That’s the purpose of the <Responsive> component, which offers a declarative approach to responsive web design.

It expects element props named small, medium, and large. It displays the element that matches the screen size (with breakpoints at 768 and 992 pixels):

// in src/posts.js
import React from 'react';
import { List, Responsive, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'admin-on-rest';

export const PostList = (props) => (
    <List {...props}>
        <Responsive
            small={
                <SimpleList
                    primaryText={record => record.title}
                    secondaryText={record => `${record.views} views`}
                    tertiaryText={record => new Date(record.published_at).toLocaleDateString()}
                />
            }
            medium={
                <Datagrid>
                    <TextField source="id" />
                    <ReferenceField label="User" source="userId" reference="users">
                        <TextField source="name" />
                    </ReferenceField>
                    <TextField source="title" />
                    <TextField source="body" />
                    <EditButton />
                </Datagrid>
            }
        />
    </List>
);

Tip: If you only provide small and medium, the medium element will also be used on large screens. The same kind of smart default exists for when you omit small or medium.

Tip: You can also use material-ui’s withWidth() higher order component to have the with prop injected in your own components.

Using a Predefined Theme

Material UI also supports complete theming out of the box. Material UI ships two base themes: light and dark. Admin-on-rest uses the light one by default. To use the dark one, pass it to the <Admin> component, in the theme prop (along with getMuiTheme()).

import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

const App = () => (
    <Admin theme={getMuiTheme(darkBaseTheme)} restClient={simpleRestClient('http://path.to.my.api')}>
        // ...
    </Admin>
);

Dark theme

Writing a Custom Theme

If you need more fine tuning, you’ll need to write your own theme object, following Material UI themes documentation. Material UI merges custom theme objects with the light theme.

import {
  cyan500, cyan700,
  pinkA200,
  grey100, grey300, grey400, grey500,
  white, darkBlack, fullBlack,
} from 'material-ui/styles/colors';
import { fade } from 'material-ui/utils/colorManipulator';
import spacing from 'material-ui/styles/spacing';

const myTheme = {
    spacing: spacing,
    fontFamily: 'Roboto, sans-serif',
    palette: {
      primary1Color: cyan500,
      primary2Color: cyan700,
      primary3Color: grey400,
      accent1Color: pinkA200,
      accent2Color: grey100,
      accent3Color: grey500,
      textColor: darkBlack,
      alternateTextColor: white,
      canvasColor: white,
      borderColor: grey300,
      disabledColor: fade(darkBlack, 0.3),
      pickerHeaderColor: cyan500,
      clockCircleColor: fade(darkBlack, 0.07),
      shadowColor: fullBlack,
    },
};

The muiTheme object contains the following keys:

  • spacing can be used to change the spacing of components.
  • fontFamily can be used to change the default font family.
  • palette can be used to change the color of components.
  • zIndex can be used to change the level of each component.
  • isRtl can be used to enable the right to left mode.
  • There is also one key for each component so you can use to customize them individually:
    • appBar
    • avatar

Tip: Check Material UI custom colors documentation to see pre-defined colors available for customizing your theme.

Once your theme is defined, pass it to the <Admin> component, in the theme prop (along with getMuiTheme()).

const App = () => (
    <Admin theme={getMuiTheme(myTheme)} restClient={simpleRestClient('http://path.to.my.api')}>
        // ...
    </Admin>
);

Using a Custom Layout

Instead of the default layout, you can use your own component as the admin layout. Just use the appLayout prop of the <Admin> component:

// in src/App.js
import MyLayout from './MyLayout';

const App = () => (
    <Admin appLayout={MyLayout} restClient={simpleRestClient('http://path.to.my.api')}>
        // ...
    </Admin>
);

Use the default layout as a starting point for your custom layout. Here is a simplified version (with no responsive support):

// in src/MyLayout.js
import React, { createElement, Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import CircularProgress from 'material-ui/CircularProgress';
import {
    AdminRoutes,
    AppBar,
    Menu,
    Notification,
    Sidebar,
    setSidebarVisibility,
} from 'admin-on-rest';

const styles = {
    wrapper: {
        // Avoid IE bug with Flexbox, see #467
        display: 'flex',
        flexDirection: 'column',
    },
    main: {
        display: 'flex',
        flexDirection: 'column',
        minHeight: '100vh',
    },
    body: {
        backgroundColor: '#edecec',
        display: 'flex',
        flex: 1,
        overflowY: 'hidden',
        overflowX: 'scroll',
    },
    content: {
        flex: 1,
        padding: '2em',
    },
    loader: {
        position: 'absolute',
        top: 0,
        right: 0,
        margin: 16,
        zIndex: 1200,
    },
};

class MyLayout extends Component {
    componentWillMount() {
        this.props.setSidebarVisibility(true);
    }

    render() {
        const {
            children,
            customRoutes,
            dashboard,
            isLoading,
            logout,
            menu,
            title,
        } = this.props;
        return (
            <MuiThemeProvider>
                <div style={styles.wrapper}>
                    <div style={styles.main}>
                        <AppBar title={title} />
                        <div className="body" style={styles.body}>
                            <div style={styles.content}>
                                {children}
                            </div>
                            <Sidebar>
                                {createElement(menu || Menu, {
                                    logout,
                                    hasDashboard: !!dashboard,
                                })}
                            </Sidebar>
                        </div>
                        <Notification />
                        {isLoading && (
                            <CircularProgress
                                color="#fff"
                                size={30}
                                thickness={2}
                                style={styles.loader}
                            />
                        )}
                    </div>
                </div>
            </MuiThemeProvider>
        );
    }
}

MyLayout.propTypes = {
    authClient: PropTypes.func,
    customRoutes: PropTypes.array,
    dashboard: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
    isLoading: PropTypes.bool.isRequired,
    menu: PropTypes.element,
    resources: PropTypes.array,
    setSidebarVisibility: PropTypes.func.isRequired,
    title: PropTypes.string.isRequired,
};

const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
export default connect(mapStateToProps, { setSidebarVisibility })(MyLayout);