A Vivid birthday

How to Build a Vivid Birthday Quiz in 20 minutes?

Vivid, Vonage’s design system, is now published. What better way to celebrate Vivid’s public birthday than to build a birthday quiz? Let’s have a vivid birthday!

Vivid is an open-source vanilla JavaScript design system built upon the web components technology. It holds a growing repertoire of web components that a developer can use regardless of framework. Its theming system allows you to tweak it to your brand easily.

We’ve just launched vivid V3. That also means it’s kind-of vivid’s birthday.

Birthday Present

One of our team members had a birthday lately, and we thought to combine the two parties. We purchased a fancy voucher and thought of a way to give it to her that’s not just a simple dry email.

Why make it easy on the birthday person? Let’s create a vivid-powered birthday quiz and let her work hard to get her birthday present!

If you are eager to see what we’re going to build, check it out:

See the Pen Birthday Quiz by Yonatan Kra (@yonatankra) on CodePen.

Find a Quiz You Like

The first step is to find questions. Google is a great helper. Our beloved team member is a CSS groupie, so I found this CSS jokes website.

Quiz Data Model

I chose a few of the jokes and put them into a data model (which is a fancy way to say: JSON):

const QUESTIONS = [
{
id: '1',
selector: '#wife',
properties: ['right', 'margin'],
},
{
id: '2',
selector: '#tower-of-pise',
properties: ['font-style'],
},
{
id: '3',
selector: '#titanic',
properties: ['float'],
},
{
id: '4',
selector: '#moses > .sea',
properties: ['column-count'],
},
];
const ANSWERS = [
['100%0', '100%none'], ['italic'], ['none'], ['2']
];

The model above holds four questions and four correct answers. I’ve separated the questions from the answers because, in a fullstack implementation, I’d probably keep the answers away from the frontend side (we are sending this to a developer proficient in chrome dev tools…).

Now that we have our questions let’s move on.

Quiz Design

Designing something with Vivid is easy, even if you lack any UX skills (like me). I quickly found the components I needed to use from the docs (and yes, the fact that I developed some of them myself helped a bit 😉 ):

  • Layout – to position elements easily on the page
  • Dialog – to show feedback to the user
  • Card – to show the question over a nice elevated surface
  • Text-field – a way to add the answers
  • Button – a nicely designed interaction components

Here’s how I composed them together:

In this design, the user sets the answers inside the text-fields and submits them to move on to the next step. Note that we can have multiple answers as in the image above (both right and margin).

If the answer is incorrect, a dialog appears:

Adding the Components

I begin with a simple HTML page and add the needed files for it.

Import the CSS files

In the head section, I imported the needed styles. In this case I’m using the CDN:

<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/@vonage/vivid/styles/tokens/theme-light.css">
<link rel="stylesheet" href="https://unpkg.com/@vonage/vivid/styles/core/all.css">

Import the JavaScript Files

In the body part I added a script tag of type module and import the JavaScript files:

<script type="module">
import "https://unpkg.com/@vonage/vivid/button";
import "https://unpkg.com/@vonage/vivid/layout";
import "https://unpkg.com/@vonage/vividcard";
import "https://unpkg.com/@vonage/vivid/text-field";
import "https://unpkg.com/@vonage/vivid/dialog";
</script>

Quiz Layout

Above the script tag, I added the quiz’s layout:

<vwc-layout>
<vwc-card>
<div slot="main">
<h2>Hi Rachel!</h2>
<p>You like riddles, and we don't want to make it easy on you to get your bday present so…</p>
<p>The moment you are ready, start the game and work for your prize!</p>
<vwc-button appearance="filled" id="startButton" label="Start the Game!"></vwc-button>
</div>
</vwc-card>
</vwc-layout>
<vwc-dialog></vwc-dialog>
view raw layout.html hosted with ❤ by GitHub

Birthday Logic

Now that we have the components and layout, we need to implement the quiz itself.

Start the Quiz

For this I’ll create a start function that will be activated by a click on the button:

function start() {
  addNextQuestion(QUESTIONS[0]);
}
startButton.addEventListener('click', start);

We’ll create the function addNextQuestion that accepts a question and shows it on screen:

function addNextQuestion(question) {
const cardContent = generateCardContent(question);
card.innerHTML = cardContent;
}

This function generates the question’s HTML and replaces the card’s HTML with it. Now we need to implement the generateCardContent function:

function generateCardContent(question) {
return `
<div slot="main">
<pre>
${question.selector} {
${question.properties.reduce((res, val) => {
return `${res}
${val}:
`
}, '')}
}
</pre>
${question.properties.reduce((res, val) => {
return `
${res}
<vwc-text-field label="${val}" id="${val}"></vwc-text-field>
`
}, '')}
<div style="text-align: center; margin-top: 5px;">
<vwc-button appearance="filled" connotation="cta" onclick="verifyAnswer(${question.id})" label="Submit"></vwc-button>
</div>
</div>
`;
}

This function returns a string with (hopefully) valid HTML. It adds a div that will enter the main slot of the card. Inside we add the CSS code of the question as preformatted text (the pre tag).

Note the use of reduce on the question’s properties which returns the valid structure for the view we want. Another reduce after the pre on the properties adds the text fields for the answers.

The final step is the submit button.

So for the question:

                {
                    id: '1',
                    selector: '#wife',
                    properties: ['right', 'margin'],
                }

We will get the following view:

Answer Validation

The submit button has a function attached to it via HTML binding onclick. When it is clicked, a getAnswer function is called with question.id as a parameter. Let’s implement it now:

async function verifyAnswer(question) {
const answer = prepareAnswerFromTextFields();
const response = {};
const questionIndex = QUESTIONS.findIndex(x => x.id === question.toString());
response.error = !ANSWERS[questionIndex].includes(answer);
if (response.error) {
error(response.error);
} else {
success();
addNextQuestion(QUESTIONS[questionIndex + 1]);
}
}
view raw verifyAnswer.js hosted with ❤ by GitHub

This function grabs the response from the text fields in the card via the prepareAnswerFromTextFields function (see the implementation here).

It then finds the question’s index and verifies the answer vs. the model.

If the error property of the response is true, we call the error method with the error.

Otherwise, we call success and move to the next question.

Error and success control the dialog and look like this:

function error(errorMessage) {
dialog.headline = "Wrong Answer!"
dialog.text = "Close this dialog and try again please";
dialog.showModal();
}
view raw error.js hosted with ❤ by GitHub
function success() {
dialog.headline = "Success!"
dialog.text = "Close this for the next question!";
dialog.showModal();
}
view raw succes.js hosted with ❤ by GitHub

The Winning State

We have one final state – to show our happy colleague her present!

Let’s add a new type of question called reward:

{

id: ‘5’,

rewardUrl: ‘https://www.canva.com/design/DAFbHyox0hc/_-bdsK86jcCqTu_21kjj9w/view’

}

In our addNextQuestion function we can add the logic to handle it:

function addNextQuestion(question) {
if (question.rewardUrl) {
showWinningModal(question.rewardUrl);
} else {
const cardContent = generateCardContent(question);
card.innerHTML = cardContent;
}
}
function showWinningModal(rewardUrl) {
dialog.headline = 'You Made It!';
dialog.icon = 'surprised-solid';
dialog.innerHTML = `
<div slot="content" style="text-align: center;">
<h2> Congratulations! </h2>
<p>
<a href=${rewardUrl}>
<vwc-button appearance='filled' connotation="success" label="Get Your Prize!!!"></vwc-button>
</p>
</a>
</div>
`;
dialog.showModal();
}

addNextQuestion now handles a question with a rewardUrl property and calls showWinningModal to give the winner her prize:

Click the button to get the prize!

Here’s the full code:

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/@vonage/vivid/styles/fonts/spezia.css">
<link rel="stylesheet" href="https://unpkg.com/@vonage/vivid/styles/tokens/theme-light.css">
<link rel="stylesheet" href="https://unpkg.com/@vonage/vivid/styles/core/all.css">
<style>
.vvd-root vwc-card h2,
.vvd-root vwc-card p {
margin: 0;
}
.vvd-root vwc-card vwc-button {
display: block;
}
</style>
</head>
<body class="vvd-root">
<vwc-layout>
<vwc-card>
<vwc-layout gutters="medium" slot="main">
<h2>Hi Rachel!</h2>
<p>You like riddles, and we don't want to make it easy on you to get your bday present so…</p>
<p>The moment you are ready, start the game and work for your prize!</p>
<vwc-button appearance="filled" id="startButton" label="Start the Game!"></vwc-button>
</vwc-layout>
</vwc-card>
</vwc-layout>
<vwc-dialog></vwc-dialog>
<script type="module">
import "https://unpkg.com/@vonage/vivid/button";
import "https://unpkg.com/@vonage/vivid/layout";
import "https://unpkg.com/@vonage/vivid/card";
import "https://unpkg.com/@vonage/vivid/text-field";
import "https://unpkg.com/@vonage/vivid/dialog";
const layout = document.querySelector('vwc-layout');
const dialog = document.querySelector('vwc-dialog');
const card = document.querySelector('vwc-card');
const QUESTIONS = [
{
id: '1',
selector: '#wife',
properties: ['right', 'margin'],
},
{
id: '2',
selector: '#tower-of-pise',
properties: ['font-style'],
},
{
id: '3',
selector: '#titanic',
properties: ['float'],
},
{
id: '4',
selector: '#moses > .sea',
properties: ['column-count'],
},
{
id: '5',
rewardUrl: 'https://www.canva.com/design/DAFbHyox0hc/_-bdsK86jcCqTu_21kjj9w/view'
}
];
const ANSWERS = [
['100%0', '100%none'], ['italic'], ['none'], ['2']
];
function start() {
addNextQuestion(QUESTIONS[0]);
}
async function verifyAnswer(question) {
const answer = prepareAnswerFromTextFields();
const response = {};
const questionIndex = QUESTIONS.findIndex(x => x.id === question.toString());
response.error = !ANSWERS[questionIndex].includes(answer);
if (response.error) {
error(response.error);
} else {
success();
addNextQuestion(QUESTIONS[questionIndex + 1]);
}
}
function error(errorMessage) {
dialog.headline = "Wrong Answer!"
dialog.text = "Close this dialog and try again please";
dialog.showModal();
}
function success() {
dialog.headline = "Success!"
dialog.text = "Close this for the next question!";
dialog.showModal();
}
function prepareAnswerFromTextFields() {
return Array.from(card.querySelectorAll('vwc-text-field')).reduce((res, val) => res += val.value, '')
}
function generateCardContent(question) {
return `
<vwc-layout gutters="large" slot="main">
<pre>
${question.selector} {
${question.properties.reduce((res, val) => {
return `${res}
${val}:
`
}, '')}
}
</pre>
${question.properties.reduce((res, val) => {
return `
${res}
<vwc-text-field label="${val}" id="${val}"></vwc-text-field>
`
}, '')}
<div style="text-align: center; margin-top: 5px;">
<vwc-button appearance="filled" connotation="cta" onclick="verifyAnswer(${question.id})" label="Submit"></vwc-button>
</div>
</vwc-layout>
`;
}
function addNextQuestion(question) {
if (question.rewardUrl) {
showWinningModal(question.rewardUrl);
} else {
const cardContent = generateCardContent(question);
card.innerHTML = cardContent;
}
}
function showWinningModal(rewardUrl) {
dialog.headline = 'You Made It!';
dialog.icon = 'surprised-solid';
dialog.innerHTML = `
<div slot="content" style="text-align: center;">
<h2> Congratulations! </h2>
<p>
<a target="_blank" href=${rewardUrl}>
<vwc-button appearance='filled' connotation="success" label="Get Your Prize!!!"></vwc-button>
</p>
</a>
</div>
`;
dialog.showModal();
}
document.getElementById('startButton').addEventListener('click', start);
window.verifyAnswer = verifyAnswer;
</script>
</body>
</html>

And a live demo of the quiz is live on codepen: Birthday Quiz.

The Layout

The vwc-layout element is a powerful tool to help us arrange our elements on screen. By replacing the div inside the card with vwc-layout with gutters I get the following change:

And there’s no need to setup margins or anything.

Going Fullstack

Note that I’ve made verifyAnswer an async function. This is because I could easily replace lines 3-5 with an async call to a server for response.

It’s just that easy 🙂

Summary

Building a UI with a web components library is very easy, and doesn’t require any framework (not even jQuery!).

Because the elements are native, you can use them with any framework (for instance, use v-repeat for the vwc-text-field instead of the reduce function).

In addition, the layout element is very powerful if you want to quickly arrange your elements nicely, as I did in the end result.

Wanna give it a go? Checkout Vivid’s components and build something nice to your loved one(s).

Thanks a lot to Rachel B. Tannenbaum and Yinon Oved for the kind and thorough feedback.

5 1 vote
Article Rating
Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Yinon
1 year ago

having such a library providing all the UI is a life saver! great serve yonatankra