pwd > ninjaPixel/v2/man-drawer/react-native-notes-003

React Native Notes – 003

Published

Button layout patterns in iOS

While building Wobble Time I've put buttons in the navigation bar of my app; sometimes there will be just just one button which leads to the question – should it be on the left or the right? Sometimes there will be two buttons in the navigation bar, which leads to the question – should the primary button be on the left or the right?

I haven't been able to find a satisfactory answer in Apple's Human Interface Guidelines regarding button placement in the navigation bar. As developers and designers we have the ability to use different coloured text in these buttons, different weight text and we can also position them on the left or right of the navigation bar.

I've gone through some of Apple's own apps to try and figure out a pattern, take a look at these screenshots:

Fig 1: Notes app uses bold font for the primary action. Both buttons use an accent colour. Primary button on the right.
Fig 1: Notes app uses bold font for the primary action. Both buttons use an accent colour. Primary button on the right.
Fig 2: Health app is similar to the Notes app with the Primary button on the right and with bold weight. Both buttons
use accent colour.
Fig 2: Health app is similar to the Notes app with the Primary button on the right and with bold weight. Both buttons use accent colour.
Fig 3: A date selection screen in Apple's fitness app. Both buttons have a bold font weight and both use an accent
colour.
Fig 3: A date selection screen in Apple's fitness app. Both buttons have a bold font weight and both use an accent colour.
Fig 4: The Fitness app again. This time the only button is an icon button. It is coloured using the secondary and
tertiary label colours. It is placed on the right.
Fig 4: The Fitness app again. This time the only button is an icon button. It is coloured using the secondary and tertiary label colours. It is placed on the right.
Fig 5: The shortcuts app. Uses an icon button on the right and text button on the left. Both are normal weight and use
the accent colour.
Fig 5: The shortcuts app. Uses an icon button on the right and text button on the left. Both are normal weight and use the accent colour.
Fig 6: After pressing the "Edit" button in Fig 5, its style changes to be a primary button (i.e. bold font weight) and
the text changes to "Done". Up until now, Done buttons have always appeared on the right, but here it makes for a nice,
reversible, experience by keeping it in the same location at the Edit button.
Fig 6: After pressing the "Edit" button in Fig 5, its style changes to be a primary button (i.e. bold font weight) and the text changes to "Done". Up until now, Done buttons have always appeared on the right, but here it makes for a nice, reversible, experience by keeping it in the same location at the Edit button.
Fig 7: Watch app places "Done" on the left and doesn't use bold text.
Fig 7: Watch app places "Done" on the left and doesn't use bold text.
Fig 8: Editing my home screen - 'gray' and 'tinted' buttons used. The primary button is tinted.
Fig 8: Editing my home screen - 'gray' and 'tinted' buttons used. The primary button is tinted.
Fig 9: The App store uses a tinted button to display the price, along with two lines of supplementary text.
Fig 9: The App store uses a tinted button to display the price, along with two lines of supplementary text.

These 7 screen shots show that there is variability in how Apple is styling navigation bar buttons, but some loose patterns to follow when creating iOS navigation bar buttons are:

  1. Use 'plain' style buttons
    1. You can use text or icons for this
    2. Use "Title Style" text
  2. Use the app's accent colour for the button
    1. Some minor operations may use secondary and tertiary label colours (or red for delete-like actions)
  3. If there are two buttons, only have one primary one
  4. The primary button should use a bold font weight
  5. The primary button is generally on the right
    1. but put it on the left if that makes for a better UX!

"Done" is quite a common action button to implement in the navigation bar. Looking at Apple's own apps, you can see that they don't consistently place this action on the same side. For me, it makes most sense to put "Done" on the left when it's effect is similar to a back button, otherwise put it on the right.

Implementation with React Native

When it comes to putting this into practise, the obvious choice is using a Button component however, the React Native Button scales its text – seemingly with no bounds – as the user increases their font size preferences. Unfortunately, there is no maxFontSizeMultiplier prop (or similar) that we can apply to cap the size of the button. Additionally, there is no way to alter the font weight used for the text. This is an issue because the Human Interface Guidelines state that a bold weight is used for a primary button. So the Button really fails us on two fronts.

Fig 10: Left shows how my unoptimised app reacted to the largest dynamic font size in iOS. Middle image is from the iOS
settings, note how Apple have capped the max size of the Back button. Right shows my updated app.
Fig 10: Left shows how my unoptimised app reacted to the largest dynamic font size in iOS. Middle image is from the iOS settings, note how Apple have capped the max size of the Back button. Right shows my updated app.

Fig 10 shows how the React Native Button component reacts when the user has upped their preferred type size; it's far too large for the navigation bar, especially when compared to "Back" button in the Settings app which has been capped at an upper limit. Also note how the "lap time" (in orange) has scaled from an already large font size to something that is obscene and, frankly, ruins the app. On the right of Fig 10 is my updated app; the "New" button is capped, matching what Apple do; text within the screen is also capped. Note that you don't have to scale all the text in your app by the same amount, the less important stuff doesn't need as much (if any) scaling.

Prioritize important content when responding to text-size changes. Not all content is equally important. When someone chooses a larger text size, they typically want to make the content they care about easier to read; they don’t always want to increase the size of every word on the screen. For example, when people choose a large accessibility text size, Mail displays the subject and body of the message in the large size, but leaves less important text — such as the date and the sender — in a smaller size. Source: Human Interaction Guidelines.

My customised button is a Pressable component wrapping some Text. This works nicely in the navigation bar and also throughout the app:

export function AppButton(props: AppButtonProps) {  
  const {  
    fontWeight,  
    color = rawColors.primary600,  
    title,  
    onPress,  
    style = { color: "inherit" }, // noop to make TS happy that this is a style (casting to a ViewStyle doesn't work)  
    ...rest  
  } = props;  
  
  // testing shows that this value, combined with the font size of 18, is the largest font size  
  // that Apple use in their own buttons when the a11y dynamic font size is maxed-out.  const maxFontSizeMultiplier = 1.2;  
  
  return (  
    <Pressable  
      accessibilityRole="button"  
      onPress={onPress}  
      hitSlop={60}  
      {...rest}  
      style={(state) => {  
        return {  
          opacity: state.pressed ? 0.5 : 1,  
          ...style,  
        };  
      }}  
    >  
      <Text  
        style={[appButtonStyles.text, { color, fontWeight }]}  
        maxFontSizeMultiplier={maxFontSizeMultiplier}  
        // Apple buttons never drop below this size. Unfortunately, this prop doesn't appear to be respected, or I misunderstand what it does.  
        minimumFontScale={18}  
      >  
        {title}  
      </Text>  
    </Pressable>  
  );  
}  
  
export function AppHeaderButtonPrimary(props: AppButtonProps) {  
  return (  
    <AppButton {...props} color={rawColors.primary600} fontWeight={"600"} />  
  );  
}  
  
const appButtonStyles = StyleSheet.create({  
  text: {  
    // this is the font size that Apple use in their own buttons  
    fontSize: 18,  
  },  
});