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.

Rendering repetitive content

Most designs contain some degree of repeating content. For example, let’s say we have a list of basic cards:
Lead DesignerMelbourne
Senior DeveloperSydney
Product ManagerCanberra
<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 DesignerMelbourne
Senior DeveloperSydney
Product ManagerCanberra
{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="small">
  {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 DesignerMelbourne
FeaturedSenior DeveloperSydney
Product ManagerCanberra
{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="small">
  {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>

State management

Using field state

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:
<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>
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.
<Actions>
  <Button onClick={() => toggleState("myDrawer")}>Open drawer</Button>
</Actions>

<Drawer title="Drawer" stateName="myDrawer">
  <Placeholder height={100} />
</Drawer>

Navigating between screens

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" && (
  <Stack space="large">
    <Heading level="2">Home</Heading>
    <Actions>
      <Button onClick={() => setState("screen", "Welcome")}>Sign in</Button>
    </Actions>
  </Stack>
)}

{getState("screen") === "Welcome" && (
  <Stack space="large">
    <Heading level="2">👋 Welcome!</Heading>
    <Placeholder height={100} />
    <Actions>
      <Button onClick={() => resetState("screen")}>Sign out</Button>
    </Actions>
  </Stack>
)}

Playroom-only components

To better facilitate prototyping, Braid provides a few extra components that are only available within Playroom. These components are designed to help you quickly build either wireframes or more realistic prototypes without needing to create them from scratch.

Placeholder

For wireframing or scaffolding page layout around a prototype.

Size

Use height and width props to control the size. Any numerical value will be treated as pixels, while strings can use any valid CSS unit, i.e. “auto” or “100%”. The default height is 120px, and width is “auto”.
Open in Playroom

Label

Use label to provide a descriptive text for the placeholder.
Sidebar
Content
Open in Playroom

Shape

Use shape choose between a rectangle (default) or round.
Open in Playroom

Images

Use image to apply a background image that fills the placeholder. The imageSize prop controls how the image is sized and accepts any valid CSS background-size value, defaulting to “cover”.
Open in Playroom

PlaceholderHeader

For placeholder for framing SEEK-based experiences in prototypes.

brand

Sets the logo for the selected brand. By default uses seek, with options for jobsdb and jobstreet.



Open in Playroom

authenticated

By default, presents a basic logged in user account menu. To prototype a logged out experience, set the authenticated prop to false .

Open in Playroom

product

Sets the product scope to be used alongside the logo.
product

Open in Playroom

divider

By default, the header uses a bottom divider, which can be disabled by setting the divider prop to false.
Open in Playroom

PlaceholderFooter

For placeholder for framing SEEK-based experiences in prototypes.

divider

By default, the footer uses a top divider, which can be disabled by setting the divider prop to false.
Open in Playroom

Custom styling

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!