Playroom Prototyping

This guide aims to teach you how to make better use of Playroom for real-world prototyping, and assumes you already have a basic level of familiarity with Playroom.
If you’re not a developer, don’t feel like you need to understand all of the syntax on this page. When you’re getting started, it’s perfectly normal to copy-and-paste code snippets without fully grasping how they work.
When laying out a prototype you may wish to reserve space that would normally be occupied by other elements in the real product. For this we provide a special Playroom-only component called Placeholder.
A Placeholder can be given a height and/or width, as well as an optional label. You can even change the shape, choosing between round and rectangle.
Side bar
Senior Developer
Sydney
<Columns space="medium">
  <Column width="1/4">
    <Placeholder height="100%" label="Side bar" />
  </Column>
  <Column>
    <Stack space="medium">
      <Placeholder height={80} shape="round" width={80} />
      <Card>
        <Stack space="small">
          <Text weight="strong">Senior Developer</Text>
          <Text>Sydney</Text>
        </Stack>
      </Card>
      <Placeholder height={80} />
    </Stack>
  </Column>
</Columns>
Placeholder can be used to add images to your prototype as well, by simply providing a url to the image prop. By default this will fill the Placeholder with the image focusing on the center. You can customise the size via the imageSize prop, which accepts any valid background-size value.
Most designs contain some degree of repeating content. For example, let’s say we have a list of basic cards:
Lead Designer
Melbourne
Senior Developer
Sydney
Product Manager
Canberra
<Stack space="medium">
  <Card>
    <Stack space="small">
      <Text weight="strong">Lead Designer</Text>
      <Text>Melbourne</Text>
    </Stack>
  </Card>
  <Card>
    <Stack space="small">
      <Text weight="strong">Senior Developer</Text>
      <Text>Sydney</Text>
    </Stack>
  </Card>
  <Card>
    <Stack space="small">
      <Text weight="strong">Product Manager</Text>
      <Text>Canberra</Text>
    </Stack>
  </Card>
</Stack>
Notice that these cards are manually written (or more realistically, copied-and-pasted) several times. This isn’t ideal when we want to iterate on our design since we have to update each card by hand. If we had a lot of cards on screen, changing our design would get tiring pretty quickly.
Instead, to make our prototype faster to iterate on, we can map over an array of data, only having to define the UI in a single place. You can think of it like a Sketch symbol, only much more powerful.
Lead Designer
Melbourne
Senior Developer
Sydney
Product Manager
Canberra
{setDefaultState("jobs", [
  {
    id: 1,
    title: "Lead Designer",
    location: "Melbourne",
  },
  {
    id: 2,
    title: "Senior Developer",
    location: "Sydney",
  },
  {
    id: 3,
    title: "Product Manager",
    location: "Canberra",
  },
])}

<Stack space="medium">
  {getState("jobs").map((job) => (
    <Card key={job.id}>
      <Stack space="small">
        <Text weight="strong">{job.title}</Text>
        <Text>{job.location}</Text>
      </Stack>
    </Card>
  ))}
</Stack>
We now have the same design as before, but expressed in a way that makes it much easier for us to change. Any updates we make to this card will now affect every card on the screen. It’s also much faster for us to make changes to the data without having to touch the UI.
Your data can also contain optional fields. For example, we may want to provide an optional featured property that toggles the card tone and the visibility of a badge.
Lead Designer
Melbourne
Featured
Senior Developer
Sydney
Product Manager
Canberra
{setDefaultState("jobs", [
  {
    id: 1,
    title: "Lead Designer",
    location: "Melbourne",
  },
  {
    id: 2,
    featured: true,
    title: "Senior Developer",
    location: "Sydney",
  },
  {
    id: 3,
    title: "Product Manager",
    location: "Canberra",
  },
])}

<Stack space="medium">
  {getState("jobs").map((job) => (
    <Card key={job.id} tone={job.featured ? "promote" : undefined}>
      <Stack space="small">
        {job.featured && (
          <Badge weight="strong" tone="promote">
            Featured
          </Badge>
        )}

        <Text weight="strong">{job.title}</Text>
        <Text>{job.location}</Text>
      </Stack>
    </Card>
  ))}
</Stack>
Braid components, when used in Playroom, manage their own state internally by default. For example, if you use a Checkbox component, you’re able to toggle the checkbox on and off.
<Checkbox label="Checkbox" />
This checkbox works in isolation, but what if we wanted it to control other parts of the UI? Well, first we need to provide a stateName prop to our checkbox, which then allows us to ask for its state elsewhere in our prototype using the getState function.
As a minimal example, let’s make the checkbox toggle the visibility of another element:
<Stack space="medium">
  <Checkbox label="Checkbox" stateName="myCheckbox" />

  {getState("myCheckbox") && (
    <Notice tone="positive">
      <Text>Good job! You checked the checkbox!</Text>
    </Notice>
  )}
</Stack>
We could also use the checkbox state to toggle the visibility of two alternate elements:
<Stack space="medium">
  <Checkbox label="Checkbox" stateName="myCheckbox" />

  {getState("myCheckbox") ? (
    <Notice tone="positive">
      <Text>Good job! You checked the checkbox!</Text>
    </Notice>
  ) : (
    <Notice tone="critical">
      <Text>Oops! You haven’t checked the checkbox!</Text>
    </Notice>
  )}
</Stack>
By default, all state values are blank until the user interacts with something. While this is usually fine in simple prototypes, you’re likely to find scenarios where you need the state to have a default value. For example, we might want our checkbox to be checked by default. To support this, our Playrooom provides a setDefaultState function which should be called before rendering anything to the screen:
{setDefaultState("myCheckbox", true)}

<Stack space="medium">
  <Checkbox label="Checkbox" stateName="myCheckbox" />

  {getState("myCheckbox") ? (
    <Notice tone="positive">
      <Text>Good job! You checked the checkbox!</Text>
    </Notice>
  ) : (
    <Notice tone="critical">
      <Text>Oops! You haven’t checked the checkbox!</Text>
    </Notice>
  )}
</Stack>
We can bind to more complicated state too, like text values within TextField components:
<Card>
  <Stack space="large">
    <TextField label="First name" stateName="firstName" />
    <TextField label="Last name" stateName="lastName" />

    {getState("firstName") && getState("lastName") ? (
      <Heading level="4">
        👋 Hello {getState("firstName")} {getState("lastName")}!
      </Heading>
    ) : null}
  </Stack>
</Card>
It’s not just about form elements either. For example, we might want to provide a Button that, via an onClick handler, toggles the open state of a Drawer.
In this example we’re making use of the toggleState function to set the state to true if the drawer is hidden.
<Card>
  <Actions>
    <Button onClick={() => toggleState("myDrawer")}>Open drawer</Button>
  </Actions>

  <Drawer title="Drawer" stateName="myDrawer">
    <Placeholder height={100} />
  </Drawer>
</Card>
We can also leverage state to simulate having multiple screens by using a piece of state called screen.
In this example we’re making use of the setState function to choose the desired screen, and the resetState function to go back to the original screen.

Home

{setDefaultState("screen", "Home")}

{getState("screen") === "Home" && (
  <Card>
    <Stack space="large">
      <Heading level="3">Home</Heading>
      <Actions>
        <Button onClick={() => setState("screen", "Welcome")}>Sign in</Button>
      </Actions>
    </Stack>
  </Card>
)}

{getState("screen") === "Welcome" && (
  <Card>
    <Stack space="large">
      <Heading level="3">👋 Welcome!</Heading>
      <Placeholder height={100} />
      <Actions>
        <Button onClick={() => resetState("screen")}>Sign out</Button>
      </Actions>
    </Stack>
  </Card>
)}
For custom design elements, you can use the Box component. In addition to its usual set of styling properties, it can also be further customised via the style prop which accepts an object of style rules.
Within these styles you also have access to the vars and responsiveValue APIs which allow you to make your custom styles themed and responsive. For example, if we wanted to responsively change the colour of an element:
Responsive background
<Box
  padding="large"
  borderRadius="standard"
  background={responsiveValue({
    mobile: "customDark",
    tablet: "customLight",
  })}
  style={{
    background: responsiveValue({
      mobile: vars.backgroundColor.brand,
      tablet: vars.backgroundColor.surface,
    }),
  }}
>
  <Text>Responsive background</Text>
</Box>

What’s next?

If you’ve come this far, it’s likely that you’ll still have some questions. Please reach out so we can give you a hand, and hopefully feed improvements back to the site. We’re going to keep iterating on the prototyping experience over time, so any feedback you have would be greatly appreciated!