Responsive and adaptive development
Looking for design guidelines? See Responsive and adaptive design.
Terms
Jutro components can be:
- responsive: The component adjusts to the screen size automatically using media queries.
- adaptive: The component does not adjust to the screen size, and instead you should configure them using the breakpoint props. The component renders differently depending on the device.
- fixed: The component does not adjust to the screen size.
In Storybook, each component is labelled as responsive, adaptive, or fixed in the top bar.
While most components are responsive across all types of devices, some might not be as convenient to use the same way on small devices as they are on large ones. We recommend you configure these components using breakpoint props.
How to work with designers
A designer can provide you with app designs for different breakpoints. Ask them about expected behaviors and so on, to make sure you understand how the user can get the most out of the application.
The following are general tips for working with designers:
- Look for designs of the same screen on different screens.
- Designers may use the term "form factor" to mean something similar to breakpoint, for example, desktop, tablet, and so on.
- Use "inspect" tools in the design app (for example Figma) to find out the requested spacing and sizes. Make sure to use Jutro spacing and sizing variables (see Theming).
- Show your app to a designer as soon as you can to get feedback and implement more features iteratively.
- Get clarification on items you do not understand or are not sure how they should work.
- Use developer tools in your browser to test layout on different breakpoints.
- If the application is required to work on phones, develop for phone first and move to larger sizes later.
Feasibility
Review the designs with the designer and work on feasibility. This process is also known as "UI QA" (quality assurance):
- Are all components available? For example, you are asked to provide an image carousel, but that is not a standard Jutro component. That means you will have to create your own.
- Do all components work as intended? You may need to create a POC app at this point.
- If there are complex features or interactions happening, check if they are allowed in Jutro and cost-effective to implement? For example, is the carousel the best way ot meet the use case? It will require an option to stop image switching to help with motions sickness. Is it feasible to build this functionality within the scope of your project?
Breakpoint props
The breakpoint props are:
phone
phoneWide
(mobile phone in horizontal mode)tablet
desktop
Use these props to override the behavior of your component on different devices.
Override children
For example, you want your action title bar to not include an action when the user is on a mobile device. In that case, you can use the phone
prop to override the children of the component.
<ActionTitleBar
phone={{
children: (
<TitleElement>
{/* On a phone, render only a heading */}
<h4>Just a title</h4>
</TitleElement>
),
}}>
{/* On large screens, render an icon with a tooltip and a button */}
<TitleElement>
<h4>Title</h4>
<TooltipIcon {...tooltipIconProps} />
<Button {...buttonProps}>Edit</Button>
</TitleElement>
</ActionTitleBar>
You can make most components render differently on different devices using breakpoint props.
Override props
These props override component props when rendering on a particular device. For example:
<RadioButtonField
availableValues={[
{
displayName: 'Perform payment on checkout',
},
{
displayName: 'Defer payment to a later date',
},
]}
phone={{
availableValues: [
{
displayName: 'Pay now',
},
{
displayName: 'Pay later',
},
],
}}
/>
This is a very simplistic example of displaying different labels for checkboxes on small screens. Normally, you would use intl messages to render text for a component, but this example illustrates how you would pass a copy of the availableValues
prop and override the items it contains.
Override styles
Another example is overriding styles for different devices:
<Card
className={styles.largeBanner}
phone={{
className: styles.smallBanner,
}}
tablet={{
className: styles.mediumBanner,
}}
/>
In this example, we have a card that is styled differently depending on the device size.
The useBreakpoint hook
For new components introduced in Jutro 10 as well as a general recommendation for that purpose we recommend to use useBreakpoint
hook. Breakpoint props like phone
, tablet
and phoneWide
are not available in the base configuration of new components .
The useBreakpoint
hook is exported from @jutro/layout and can be imported like import { useBreakpoint } from '@jutro/layout'
. It returns an object with the following properties:
breakpointProps
- the props you passed into the component merged with the values you overrode in one of the following props:phone
,phoneWide
,tablet
applyBreakpoint
- a function that takesprops
as an argument and returnsbreakpointProps
. You can use it in any nested code within your component where hooks are not allowed.breakpoint
- the name of the current breakpoint. Any ofphone
,phoneWide
,tablet
, ordesktop
.
There are four ways you can use the hook to get the expected result.
1. Get props from the hook for a certain breakpoint
The first way to use the hook is to pass the props and apply the output to the component.
In the following example, the phone
props object is passed to useBreakpoint
. The props are then returned as textInputPhoneProps
on the phone
breakpoint. Then they are assigned to TextInput
and the previously added props are overridden. The label
prop is overridden, then placeholder
and className
are added when the hook detects that the breakpoint changes to phone
.
const { breakpointProps: textInputPhoneProps } = useBreakpoint({
phone: {
placeholder: 'Phone placeholder',
label: 'Phone label',
className: 'somePhoneClass',
},
});
return (
<TextInput
label="Text input"
{...textInputPhoneProps}
/>
);
2. Only get breakpoint status from hook
The second way to use the hook is to only get the breakpoint status and use it to change that prop based on the returned breakpoint. In the following example, breakpoint
is obtained from the hook and it changes based on the viewport. A different value for the label
prop can be assigned if the breakpoint is set to phone
.
const { breakpoint } = useBreakpoint({});
return (
<TextInput label={breakpoint === 'phone' ? 'Phone label' : 'Text input'} />
);
3. Get the callback that returns breakpoint props
The third way to use the hook is to use applyBreakpoint
- it is very similar to the first option but can be better if there are different components on the same page that would require many useBreakPoints
instances. In the following example, the callback that is returning the props is obtained and then applyBreakpoint
is assigned to every TextInput
and different props are passed to every component.
const { applyBreakpoint } = useBreakpoint({});
return (
<React.Fragment>
<TextInput
label="Text input"
{...applyBreakpoint({
phone: {
placeholder: 'Phone placeholder',
label: 'Phone label',
className: 'somePhoneClass',
},
})}
/>
<TextInput
label="Text input instance 2"
{...applyBreakpoint({
phone: {
placeholder: 'Another Phone placeholder',
label: 'Another Phone label',
className: 'someOtherPhoneClass',
},
})}
/>
</React.Fragment>
);
4. All at once
It is possible to get all three things that are returned from useBreakpoint
and use them all at once. For example:
const {
breakpointProps: textInputPhoneProps,
breakpoint,
applyBreakpoint,
} = useBreakpoint({
phone: {
placeholder: 'Phone placeholder',
label: 'Phone label',
className: 'somePhoneClass',
},
});
return (
<React.Fragment>
<TextInput
label="Text input"
{...textInputPhoneProps}
/>
<TextInput
label={breakpoint === 'phone' ? 'Phone label 2' : 'Text input'}
/>
<TextInput
label="Text input"
{...applyBreakpoint({
phone: {
placeholder: 'Phone placeholder 3',
label: 'Phone label 3',
className: 'someOtherPhoneClass',
},
})}
/>
</React.Fragment>
);
Different props for a breakpoint
A typical use case for the hook is when you want to pass custom props to a component based on the breakpoint. When using the component, you do the following:
<BigBanner
phone={{
message: 'Call us!',
}}
message="Get in touch"
/>
That means you want your users to see the message "Get in touch" in most cases. But, when they are on the phone, you want them to see a message that says "Call us!"
Inside the functional component, you use the useBreakpoint
hook, and you pass the props as the only argument. Then, you extract the value of the message
property.
On desktop, it returns "Get in touch", and on phone, it returns "Call us!"
import { useBreakpoint } from '@jutro/layout';
function BigBanner(props) {
const { breakpointProps } = useBreakpoint(props);
return <div>{breakpointProps.message}</div>;
}
Different styles for a breakpoint
Another use case for the hook is to apply different styling to a component based on the current breakpoint.
You create two classes in your styles module:
.defaultColor {
color: magenta;
}
.phoneColor {
color: blue;
}
To use the styles, you get the current breakpoint using the hook. Then, you select a class based on the value of the hook.
import React from 'react';
import { useBreakpoint } from '@jutro/layout';
import styles from './BigBanner.module.scss';
function BigBanner(props) {
const { breakpoint } = useBreakpoint(props);
const myClass =
breakpoint === 'phone' ? styles.phoneColor : styles.defaultColor;
return <div className={myClass}>Welcome to my banner!</div>;
}
Or:
import React from 'react';
import cx from 'classnames';
import styles from './BigBanner.module.scss';
import { useBreakpoint } from '@jutro/layout';
function BigBanner(props) {
const { breakpointProps } = useBreakpoint({
...props,
className: cx(props.className, styles.defaultColor),
phone: {
className: cx(props.className, styles.phoneColor),
},
});
const { className } = breakpointProps;
return <div className={className}>{breakpointProps.message}</div>;
}
The BreakpointTracker context
You can also use BreakpointTrackerContext
and BreakpointTracker
directly. You can use them within class components or other places where using hooks is impossible.
The entire app is wrapped in this context provider, so you can use it within a class component, for example inside the render
block. You can also use it within functions that are not nested inside functional components.
BreakpointTracker.applyBreakpointOverrides
returns only a breakpointProps
object.
It takes two arguments: props
and a breakpoint
.
Breakpoint context in a functional component
import React, { useContext } from 'react';
import { BreakpointTracker, BreakpointTrackerContext } from '@jutro/layout';
function BigBanner(props) {
const breakpoint = useContext(BreakpointTrackerContext);
const someRenderFunction = () => {
const breakpointProps = BreakpointTracker.applyBreakpointOverrides(
props,
breakpoint
);
return (
<pre>
<code>{JSON.stringify(breakpointProps, null, 2)}</code>
</pre>
);
};
return <div>{someRenderFunction()}</div>;
}
Breakpoint context in a class component
import React from 'react';
import { BreakpointTracker, BreakpointTrackerContext } from '@jutro/layout';
class ClassBanner extends React.Component {
static contextType = BreakpointTrackerContext;
render() {
const breakpointProps = BreakpointTracker.applyBreakpointOverrides(
this.props,
this.context
);
return (
<pre>
<code>{JSON.stringify(breakpointProps, null, 2)}</code>
</pre>
);
}
}
export default ClassBanner;
Breakpoints in unit tests
To test breakpoints, you can use the following example as a guide. It tests the BigBanner
component which we created in other sections of this page.
import React from 'react';
import { render } from '@testing-library/react';
import { BreakpointTrackerContext } from '@jutro/layout';
import { BigBanner } from '../BigBanner';
import styles from '../BigBanner.module.scss';
function renderBigBannerWithBreakpoints(props = {}, breakpoint = 'desktop') {
return render(
<BreakpointTrackerContext.Provider value={breakpoint}>
<BigBanner {...props} />
</BreakpointTrackerContext.Provider>
);
}
describe('BigBanner', () => {
it('renders with default class on desktop', () => {
const { container } = renderBigBannerWithBreakpoints();
expect(container.firstChild).toHaveClass(styles.defaultColor);
});
it('renders with mobile class on phone', () => {
const { container } = renderBigBannerWithBreakpoints({}, 'phone');
expect(container.firstChild).toHaveClass(styles.phoneColor);
});
});
SASS helpers (mixins)
There are two SCSS mixins in @jutro/theme
which are helpful in responsive development:
gw-breakpoint-down
gw-breakpoint-up
@import "~@jutro/theme/assets/sass/helpers";
.myCustomComponentWithHelpers {
@include gw-breakpoint-up(tablet) {
/* your styles here */
}
@include gw-breakpoint-down(phone) {
/* your styles here */
}
}
Was this page helpful?