Creating UI From Scratch

While customizing Studio UI components works for most creatives, there are cases in which it’s not enough. Studio decision UI is coupled with decisions and nodes. There are scenarios in which you need to add UI that is fully controlled by your code.
The ui.add API allows adding components that can be fully controlled by the developer. In this tutorial we will implement a simple persistent timer component. This component is rendered throughout the project and serves as a single timer for all the decisions.

It is assumed that you are already familiar with the UI development concepts explained in ui for interactive video and state and redux.

The Timer

Our timer is a very basic. A background rectangle, and a progress rectangle on top of it. Two div elements and absolute positioning are enough to get started. Here is how it looks:

The timer is built on top of the decision plugin API. If you are not familiar with the usage of decisions please read the decisions tutorial first.

The flow is as follows:

  1. The timer is in idle state when there is no active decision. A simple gray rectangle.
  2. When a decision becomes active the timer expands and a green progress bar starts running.
  3. Once a button is clicked the decision associated with the button is made and the progress bar turns yellow.
  4. When the decision time ends the timer goes back to idle state.

Decisions and Redux

All the information required to know which decision is active and to visualize the time left for the decision can be found in the redux store available to React components via connect or useSelector.
First we need the current node id. The player provides this information in its reduxState:

let nodeId =
    player.ui.useSelector(globalState => globalState.player.currentNodeId);

The decision plugin provides decisions state in its reduxState. We can use the current node id to find the state of the current decision:

let decision = 
    player.ui.useSelector(globalState => (nodeId ? globalState.decision[nodeId] : null));

We can use the data available in the decision object to create the markup and styling required.

Timer component

The timer component is built using two div elements. By adding the decision’s state to the outer div’s className we can apply css rules to control the visual aspects of the different states.
The inner div is the progress. It is positioned on top of the outer div and its width is set dynamically according to the decision progress. Here is a simple, yet fully functional component.


import React from 'react';
import './Timer.scss';

export default function Timer(props){
    let player = props.player;
    let nodeId = player.ui.useSelector(globalState => globalState.player.currentNodeId);
    let decision = player.ui.useSelector(globalState => (nodeId ? globalState.decision[nodeId] : null));
    let state = decision ? decision.state : '';
    let progress = (decision ? decision.progress * 100 : 0) + "%";

    return (
        <div className={`timer ${state}`}>
            <div className='progress' style={{ width: progress }} />

Style the Timer

Our timer is composed of only two div elements. The following SASS example takes care of the following:

  1. Position the timer div on top of the video.
  2. Position the progress div on top of the timer div and make it transparent.
  3. When the timer is either active or made it expands and opacity: 1 is applied to the progress div.
  4. When the timer is made the progress color is set to yellow.


.timer {
    position: absolute;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    width: 50px;
    height: 10px;
    background-color: #333;
    transition: all .4s ease;

    .progress {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        width: 0;
        transition: all .3s ease;
        opacity: 0;
        background-color: green;

    &.active, &.made {
        transform: translateX(-50%) scale(1.5);
        .progress {
            opacity: 1;

    &.made {
        .progress {
            background-color: yellow;

Add the Timer

The last step is using the ui.add API to add a new instance of our timer component.


import Timer from './components/Timer/Timer';

export default function onReady(player) {
    player.ui.add('globalTimer', Timer);
Rate this page: X
Tell us more!