Skip to content

Writing JSX

The syntax React used for writing UI templates is called JSX. It is very similar to writing HTML but with full JavaScript capability baked into it.

Here is a simple example of adding some basic HTML elements to your widget UI using a class component:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
// in widget.tsx:
import { React, AllWidgetProps } from 'jimu-core';

export default class Widget extends React.PureComponent<AllWidgetProps<{}>, any>{
  render() {
    return <div className="myWidget">
      <p>This is a sample widget</p>
      <button type="button" style={{background: 'orange'}}>I'm a button</button>
    </div>;
  }
}

Function Components

Besides class components, you can also write your widget using a function component.

Here is the equivalent example written as a function component:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
// in widget.tsx:
import { React, AllWidgetProps } from 'jimu-core';

export default function Widget(props: AllWidgetProps<{}>) {
  return (
    <div className="myWidget">
      <p>This is a sample widget</p>
      <button type="button" style={{ background: 'orange' }}>I'm a button</button>
    </div>
  );
}

Function components can also use React hooks (such as useState and useEffect) to manage state and side effects, making them very powerful and flexible.

Output:

Simple widget UI

Using the Jimu UI library

The Jimu framework provides a UI library of components for developers to use in their widget development, such as:

  • Basic UI components: button, dropdown, inputs, icon, nav, modal, paper, etc.
  • Advanced UI components: date picker, resource selector, etc.

You can preview most of the commonly used components and icons by visiting the Storybook site: https://developers.arcgis.com/experience-builder/storybook.

Read more about Storybook in Experience Builder.

Jimu UI is the official UI library for Experience Builder, and it is highly recommended that the components from this library are considered and utilized in your UI development. The reasons are as follows:

  • UI/UX consistency: the overall look and feel of the widgets, as well as the apps created with them, follows a consistent pattern.
  • Themeable: the styles of components are configurable and themeable, which makes it easier to make your widgets compatible with different themes.
  • Better integration with Experience Builder and ArcGIS.

Import components:

The basic UI components can be directly imported from 'jimu-ui', and the advanced UI components need to be imported separately by using their paths:

Use dark colors for code blocksCopy
1
2
import { Button, Icon, Paper, TextInput } from 'jimu-ui'; // basic
import { DatePicker } from 'jimu-ui/date-picker'; // advanced

Quick sample:

Here we add a Button component with the "primary" style and an extra star icon to the widget:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// in widget.tsx:
import { React, AllWidgetProps } from 'jimu-core';
import { Button, Icon } from 'jimu-ui'; // import components
import { StarFilled } from 'jimu-icons/filled/application/star'

// Create an svg icon using Icon component:
const iconNode = <StarFilled />;

export default class Widget extends React.PureComponent<AllWidgetProps, any>{
  render(){
    // Add Button component containing an icon to the widget:
    return <Button type="primary">{iconNode} primary button</Button>;
  }
}

Output:

Button component with icon

Using Paper component as a widget container

To maintain consistent widget styling, the widget container should preferably use the Paper component.

Use dark colors for code blocksCopy
1
2
3
const Widget = () => {
  return <Paper variant="flat" shape="none" className="jimu-widget widget-xxx">...</Paper>
}

Default styles:

  • Background: theme.sys.color.surface.paper
  • Text: theme.sys.color.surface.paperText

For lighter text, use surface.paperHint, preferably via <Typography color="paperHint" />:

Use dark colors for code blocksCopy
1
<Typography color="paperHint">Secondary text</Typography>

Other options:

  • Border: <Paper variant="outlined" />
  • BorderRadius: <Paper shape="shape1" /> (default is shape2)
  • Transparent background: <Paper variant="flat" transparent />
Theme guide surface text

Using the calcite components

Jimu UI is the primary component library for Experience Builder, and you may want to use that first because it will keep your custom widgets and themes consistent with the rest of the Experience Builder theme. If you do have a requirement to use the Calcite Design System, you can do that - see the Calcite sample widget. Please note that you should import the Calcite components from calcite-components instead of @esri/calcite-components.

Style your widget

In Experience Builder, there are some options to style a widget:

CSS-in-JS refers to a way of writing CSS in JavaScript to tackle issues that CSS does not solve, such as vendor prefixes, scoped CSS, JS logics, theming capability, etc.

There are many popular CSS-in-JS libraries out there, such as Styled Components and Emotion. In Experience Builder, we use Emotion as the framework for styling and theming purposes.

Emotion provides two styling patterns:

1. css prop

The css prop from Emotion allows you to write CSS styles in a more natural and friendly way compared to React's style prop. CSS styles can be written in template literals, which allows you to write JS logics inside of CSS.

For example, the following sample Counter widget changes its text color from red to green when the count value is greater than 2:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// widget.tsx:
import { React, css, type AllWidgetProps } from 'jimu-core';
import { Button, ButtonGroup } from 'jimu-ui';

interface State {
  count: number;
}

export default class Widget extends React.PureComponent<AllWidgetProps<{}>, State>{
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }
  render() {
    const numberStyle = css`
      font-size: 2.5rem;
      color: ${this.state.count > 2 ? 'green' : 'red'};
    `;
    return <div className="text-center">
      <p css={numberStyle}>{this.state.count}</p>
      <ButtonGroup variant="outlined" color="primary">
        <Button onClick={e => {setCount(count - 1)}}> - </Button>
        <Button onClick={e => {setCount(count + 1)}}> + </Button>
      </ButtonGroup>
    </div>;
  }
}

or

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// widget.tsx:
import { React, css, type AllWidgetProps } from 'jimu-core';
import { Button, ButtonGroup } from 'jimu-ui';

const Widget = (props: AllWidgetProps<{}>): React.ReactElement => {
  const [count, setCount] = React.useState(0);
  const numberStyle = css`
    font-size: 2.5rem;
    color: ${count > 2 ? 'green' : 'red'};
  `;
  return <div className="text-center">
    <p css={numberStyle}>{count}</p>
    <ButtonGroup variant="outlined" color="primary">
      <Button onClick={e => {setCount(count - 1)}}> - </Button>
      <Button onClick={e => {setCount(count + 1)}}> + </Button>
    </ButtonGroup>
  </div>;
}

Output:

CSS prop sample

2. Styled components

This pattern is inspired by the styled-components library and the usage is very similar. The "styled" approach is perfect for creating re-usable components within your widget.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { React, AllWidgetProps } from 'jimu-core';
import { styled } from 'jimu-theme';

// A styled button component:
const StyledButton = styled.button`
  color: white;
  background-color: blue;
  transition: 0.15s ease-in all;
  &:hover {
    background-color: darkblue;
  }
`;

export default class Widget extends React.PureComponent<AllWidgetProps<{}>>{
  render() {
    return <StyledButton>
      A styled HTML Button
    </StyledButton>;
  }
}

Output:

Styled component sample

CSS utility classes:

Jimu UI provides the same CSS utility classes to quickly apply styles to UI elements.

Quick sample:

Here we add w-100, p-3, bg-paper, text-paper, border to make an element:

  • take 100% width of its parent
  • have paddings of 12px
  • use paper color from the theme as its background color
  • use paper text color from the theme as its text color
  • use divider primary color from the theme as its border color
Use dark colors for code blocksCopy
1
2
3
4
5
// in the render() function:

return <div className="w-100 p-3 bg-paper text-paper border">
  <p>This is a sample widget</p>
</div>;

Output:

Widget UI Utilities

Inline CSS

In the context of React, inline CSS styles are written as JavaScript objects and applied to the style attribute of a DOM element, such as:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// in the render() function:

const containerStyle = {
  background: 'darkblue',
  color: 'white',
  width: 200,
  height: 150,
  padding: '1rem',
  borderRadius: 5
};

return <div
  style={containerStyle} // CSS styles applied
> content </div>;

Output:

Inline CSS sample

External CSS style sheets

Another way is to define CSS styles in external stylesheet files and import them separately in the widget. Accepted stylesheet file extensions are: .css, .sass, and .scss.

Take the previous code sample as an example; we can move the CSS styles into a separate stylesheet (e.g., style.css):

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
/* style.css */
.my-widget {
  background: 'darkblue';
  color: 'white';
  width: 200px;
  height: 150px;
  padding: '1rem';
  border-radius: 5px;
}

then import the file in your widget as:

Use dark colors for code blocksCopy
1
2
// widget.tsx:
import 'path/to/style.css';

and don't forget to add the class name to the DOM element defined in the style.css:

Use dark colors for code blocksCopy
1
2
3
// widget.tsx:

return <div className="my-widget"> content </div>;

Output:

External CSS stylesheets sample

Working with theme

This is required if you would like your widget to look consistent with the rest of the application and if you would like to update the look and feel automatically when the theme changes.

Accessing theme variables from widget props

Under the hood, Experience Builder framework provides the theme variables as a JSON object and injects it into the widgets as a property. You have access to all theme variables, such as color, typography, shadow, etc.

Use props.theme to access the theme variables within your widget and reference them in your CSS declaration. For example:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { React, AllWidgetProps } from 'jimu-core';
import { css } from 'jimu-core';

export default class Widget extends React.PureComponent<AllWidgetProps<{}>>{
  render() {
    const theme = this.props.theme;
    const style = css({
      color: theme.sys.color.surface.paperText,
      backgroundColor: theme.sys.color.surface.paper,
      padding: theme.sys.spacing(3),
      borderRadius: theme.sys.shape.shape2
    });
    return <div css={style}>
      <p>This is a sample widget</p>
    </div>;
  }
}

or

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { React, css, AllWidgetProps } from 'jimu-core';

export default function Widget(props: AllWidgetProps<{}>) {
  const { theme } = props;
  const style = css({
    color: theme.sys.color.surface.paperText,
    backgroundColor: theme.sys.color.surface.paper,
    padding: theme.sys.spacing(3),
    borderRadius: theme.sys.shape.shape2
  });
  return <div css={style}>
    <p>This is a sample widget</p>
  </div>;
}

Output:

Widget with theme

Accessing theme variables in components

For more complex UIs inside a widget, you can further break them down into components that use the theme:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// my-component.tsx
import { React } from 'jimu-core';
import { styled } from 'jimu-theme'

const MyComponent = styled('div')(({ theme }) => ({
    color: theme.sys.color.surface.paperText,
    backgroundColor: theme.sys.color.surface.paper,
    padding: theme.sys.spacing(3),
    borderRadius: theme.sys.shape.shape2
}));

export default MyComponent;

//widget.tsx
import { React, AllWidgetProps } from 'jimu-core';
import MyComponent from './my-component';

export default function Widget(props: AllWidgetProps<{}>) {
  return (
    <MyComponent>
      <p>This is a sample widget</p>
    </MyComponent>
  );
}
Widget with theme

Accessing theme variables via hook

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// my-component.tsx
import { React, css } from 'jimu-core';
import { useTheme } from 'jimu-theme'

const MyComponent = ({ children }) => {
  const theme = useTheme();
  const style = css({
    color: theme.sys.color.surface.paperText,
    backgroundColor: theme.sys.color.surface.paper,
    padding: theme.sys.spacing(3),
    borderRadius: theme.sys.shape.shape2
  });
  return <div css={style}>
    {children}
  </div>;
};
export default MyComponent;

//widget.tsx
import { React, AllWidgetProps } from 'jimu-core';
import MyComponent from './my-component';

export default function Widget(props: AllWidgetProps<{}>) {
  return <MyComponent>
    <p>This is a sample widget</p>
  </MyComponent>;
}
Widget with theme

Accessing theme variables via HOC withTheme

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// my-component.tsx
import { React, css } from 'jimu-core';
import { withTheme } from 'jimu-theme'

const MyComponent = ({ theme, children }) => {

  const style = css({
    color: theme.sys.color.surface.paperText,
    backgroundColor: theme.sys.color.surface.paper,
    padding: theme.sys.spacing(3),
    borderRadius: theme.sys.shape.shape2
  });

  return <div css={style}>
    {children}
  </div>
};

export default withTheme(MyComponent)

//widget.tsx
import { React, AllWidgetProps } from 'jimu-core';
import MyComponent from './my-component';

export default function Widget(props: AllWidgetProps<{}>) {
  return <MyComponent>
    <p>This is a sample widget</p>
  </MyComponent>;
}
Widget with theme

ArcGIS Maps SDK components

The framework has handled the theme token mapping between the Jimu theme and Calcite theme, this means you don't need to care about the style of the Calcite components. However, if you don't use the Calcite components and Jimu components, for example, if you use a ArcGIS Maps SDK web component, you may need to override the tokens in your widget if it does not fit the theme.

References

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.