Example prototypes

On this page, you’ll find some examples of how you can use the iOS 13 Segmented Control component in your prototypes.

download the examples

Current option

This simple demo shows how to get the control’s currently selected option.

The component has an event callback property: onValueChange(). A function passed to that property will be called every time the selected segment changes.

The example function below is simple, it just saves the current option in appState.currentOption

import { Override, Data } from "framer"

const appState = Data({
    currentOption: null,
})

export function SegmentedControl(): Override {
    return {
        onValueChange(option, index) {
            appState.currentOption = option
        },
    }
}

… which in turn will update the text of the text block:

export function CurrentOption(): Override {
    return {
        text: appState.currentOption,
    }
}

Current index

The onValueChange() callback is always called with two parameters: option (as used above), but also index, the number of the selected tab.

Depending on what you want to accomplish, you can use one or the other.

import { Override, Data } from "framer"

const appState = Data({
    currentIndex: null,
})

export function SegmentedControl(): Override {
    return {
        onValueChange(option, index) {
            appState.currentIndex = index
        },
    }
}

export function CurrentIndex(): Override {
    return {
        text: appState.currentIndex,
    }
}

Properties demo

In this next prototype, three Segmented Controls are used to change the properties of the fourth Segmented Control at the bottom.

As a reminder, these are this component’s properties:

Property Type Description
mode boolean Light or Dark mode
options string[] Names of the (max. 5) segments
setCurrentBy boolean Whether to set the selected segment by index or name
currentIndex number Index of the selected segment
currentOption string Name of the selected segment
onValueChange() function Event callback property that shares the selected segment’s option name and index number

  

And here’s the code: There’s a Data object, called appState, in which the current mode, options and currentIndex are saved:

import { Override, Data } from "framer"

const appState = Data({
    mode: true,
    options: ["Mango", "Lemon"],
    currentIndex: null,
})

Those values are passed to the bottom Segmented Control:

export function SegmentedControl(): Override {
    return {
        mode: appState.mode,
        options: appState.options,
        setCurrentBy: true,
        currentIndex: appState.currentIndex,
    }
}

(The setCurrentBy property is simply set to true because we’re changing the selection by index number.)

When the mode switches to Dark Mode, we want the Frame under the Segmented Control to be dark as well. That happens with this Override:

export function Background(): Override {
    return {
        background: appState.mode ? "#fff" : "#000",
    }
}

The Frame’s background color will be white when appState.mode is true, and black when it’s not.

The three Segmented Controls at the top have Overrides that use the onValueChange() callback property to update the corresponding values in appState.

1. Changing the mode

export function SetMode(): Override {
    return {
        onValueChange(option, index) {
            if (index === 0) {
                appState.mode = true
            } else {
                appState.mode = false
            }
        },
    }
}

2. Changing the options

export function SetOptions(): Override {
    return {
        onValueChange(option, index) {
            if (index === 0) {
                appState.options = ["Mango", "Lemon"]
            } else if (index === 1) {
                appState.options = ["Cherry", "Kiwi", "Banana"]
            } else {
                appState.options = ["🍒", "🥝", "🍌", "🥭", "🍋"]
            }
        },
    }
}

3. Changing the currentIndex

export function SetIndex(): Override {
    return {
        onValueChange(option, index) {
            appState.currentIndex = index
        },
    }
}

Maps

When switching to the Satellite view in Apple’s Maps app, the interface will transition to Dark Mode, even when your phone is currently in Light Mode.

The mode property of the Segmented Control is a Boolean, in which true stands for light mode, and false for dark mode.

There’s a Data object with the current mode and the opacity values of the screens (which are just images):

import { Override, Data } from "framer"

const appState = Data({
    mode: true,
    opacityTransport: 0,
    opacitySatellite: 0,
})

(The first screen, Map, is placed underneath the other two, so it never needs to be hidden.)

Those opacity values are passed, in turn, to the animate property of the screens:

export function Transport(): Override {
    return {
        animate: { opacity: appState.opacityTransport },
        transition: { ease: "easeOut" },
    }
}
export function Satellite(): Override {
    return {
        animate: { opacity: appState.opacitySatellite },
        transition: { ease: "easeOut" },
    }
}

And then the screens are faded in or out depending on the current index of the Segmented Control:

export function SegmentedControl(): Override {
    return {
        mode: appState.mode,
        onValueChange(option, index) {
            appState.mode = true
            if (index === 0) {
                // Map
                appState.opacityTransport = 0
                appState.opacitySatellite = 0
            } else if (index === 1) {
                // Transport
                appState.opacityTransport = 1
                appState.opacitySatellite = 0
            } else if (index === 2) {
                // Satellite
                appState.opacitySatellite = 1
                appState.mode = false
            }
        },
    }
}

Note that in this same Override the Segmented Control’s mode is bound to appState.mode, and that the mode will always be true, except when switching to the Satellite screen.

By the way, this prototype also uses the Status Bar component from Framer’s Example Kit.

This Status Bar switches between its light and dark style with an appearance property that expects a string: "dark" or "light".

So when appState.mode is true we pass it a "dark", and when not, a "light":

export function StatusBar(): Override {
    return {
        appearance: appState.mode ? "dark" : "light",
    }
}

Steps

Next up: the Steps detail screen in the iPhone Health app.

We’re animating two things simultaneously: the number of steps above the chart and the chart itself. The steps count has a simple opacity animation, but the chart also has a width animation (scaleX) when fading in.

For the Day, Week, Month, and Year numbers, I used simple opacity values:

import { Override, Data } from "framer"

const appState = Data({
    opacityAmountDay: 1,
    opacityAmountWeek: 0,
    opacityAmountMonth: 0,
    opacityAmountYear: 0,
    variantChartDay: "visible",
    variantChartWeek: "hidden",
    variantChartMonth: "hidden",
    variantChartYear: "hidden",
})

… which I applied to the corresponding images (in an animate, so that they will fade in and out):

export function Amount_Day(): Override {
    return {
        animate: { opacity: appState.opacityAmountDay },
    }
}
export function Amount_Week(): Override {
    return {
        animate: { opacity: appState.opacityAmountWeek },
    }
}
export function Amount_Month(): Override {
    return {
        animate: { opacity: appState.opacityAmountMonth },
    }
}
export function Amount_Year(): Override {
    return {
        animate: { opacity: appState.opacityAmountYear },
    }
}

But the charts, on the other hand, have a Variants animation.

import { Override, Data } from "framer"

const appState = Data({
    opacityAmountDay: 1,
    opacityAmountWeek: 0,
    opacityAmountMonth: 0,
    opacityAmountYear: 0,
    variantChartDay: "visible",
    variantChartWeek: "hidden",
    variantChartMonth: "hidden",
    variantChartYear: "hidden",
})

… with these variant labels:

const chartVariants = {
    hidden: { opacity: 0, scaleX: 1.2, transition: { duration: 0 } },
    visible: { opacity: 1, scaleX: 1 },
}

The animation to "hidden" has a duration of 0, so it happens instantly. It also makes the chart invisible (zero opacity) and a bit wider (a scaleX of 120%).

When animated to "visible", the chart will shrink back to its real size while fading in.

The charts each get the chartVariants object as their animation variants, and the current variant label to animate to (in variantChartDay, variantChartWeek, …).

export function Chart_Day(): Override {
    return {
        variants: chartVariants,
        animate: appState.variantChartDay,
    }
}
export function Chart_Week(): Override {
    return {
        variants: chartVariants,
        animate: appState.variantChartWeek,
    }
}
export function Chart_Month(): Override {
    return {
        variants: chartVariants,
        animate: appState.variantChartMonth,
    }
}
export function Chart_Year(): Override {
    return {
        variants: chartVariants,
        animate: appState.variantChartYear,
    }
}

When the selection of the Segmented Control changes, I first hide all numbers and charts:

export function SegmentedControl(): Override {
    return {
        onValueChange(option, index) {
            // Hide all numbers
            appState.opacityAmountDay = 0
            appState.opacityAmountWeek = 0
            appState.opacityAmountMonth = 0
            appState.opacityAmountYear = 0

            // Hide all charts
            appState.variantChartDay = "hidden"
            appState.variantChartWeek = "hidden"
            appState.variantChartMonth = "hidden"
            appState.variantChartYear = "hidden"

            if (index === 0) {
                appState.opacityAmountDay = 1
                appState.variantChartDay = "visible"
            } else if (index === 1) {
                appState.opacityAmountWeek = 1
                appState.variantChartWeek = "visible"
            } else if (index === 2) {
                appState.opacityAmountMonth = 1
                appState.variantChartMonth = "visible"
            } else if (index === 3) {
                appState.opacityAmountYear = 1
                appState.variantChartYear = "visible"
            }
        },
    }
}

… but directly after that, I show the correct amount and chart:

export function SegmentedControl(): Override {
    return {
        onValueChange(option, index) {
            // Hide all numbers
            appState.opacityAmountDay = 0
            appState.opacityAmountWeek = 0
            appState.opacityAmountMonth = 0
            appState.opacityAmountYear = 0

            // Hide all charts
            appState.variantChartDay = "hidden"
            appState.variantChartWeek = "hidden"
            appState.variantChartMonth = "hidden"
            appState.variantChartYear = "hidden"

            if (index === 0) {
                appState.opacityAmountDay = 1
                appState.variantChartDay = "visible"
            } else if (index === 1) {
                appState.opacityAmountWeek = 1
                appState.variantChartWeek = "visible"
            } else if (index === 2) {
                appState.opacityAmountMonth = 1
                appState.variantChartMonth = "visible"
            } else if (index === 3) {
                appState.opacityAmountYear = 1
                appState.variantChartYear = "visible"
            }
        },
    }
}

Leave a Reply