rquiz provides three types of interactive quizzes as HTML widgets:
| Function | Description |
|---|---|
singleQuestion() |
A single question with instant feedback (single- or multiple-choice) |
multiQuestions() |
A multi-question quiz with navigation, timer, and results |
fillBlanks() |
A fill-in-the-blank cloze text |
All quizzes work in R Markdown, Quarto documents, HTML presentations (e.g. revealJS), and Shiny applications.
singleQuestion)Questions are defined as a named list with 3 (or 4) elements:
myQuestionSC <- list(
question = "Which city hosted the 2024 Summer Olympics?", # the question text
options = c("Berlin", "London", "Paris", "Madrid"), # answer options
answer = 3, # index of the correct answer (Paris)
tip = "Think about the Eiffel Tower." # optional tip text
)For single-choice, answer is a single
integer. For multiple-choice, answer is a
vector of integers. The quiz mode is auto-detected from the length of
answer:
myQuestionMC <- list(
question = "Which of the following are prime numbers?",
options = c("2", "4", "7", "9", "11", "15"),
answer = c(1, 3, 5), # indices of correct options (2, 7, 11)
tip = "Prime numbers are only divisible by 1 and themselves."
)You can validate your question with
checkSingleQuestion() before passing it to the quiz
function. This catches common mistakes like missing fields, out-of-range
indices, or misspelled element names:
checkSingleQuestion(myQuestionSC) # returns invisibly if valid
# Common mistake: unnamed or partially named list
bad <- list("What is 2+2?", c("3", "4"), answer = 2)
checkSingleQuestion(bad)
#> Error: The question list must have named elements: 'question', 'options',
#> and 'answer' (plus optionally 'tip'). Missing: question, options.
#> Example: list(question = "What is 2+2?", options = c("3", "4", "5"),
#> answer = 2)
#> See ?checkSingleQuestion or ?singleQuestion for examples.multiQuestions)Pass a list of question lists. Each question follows the same
structure. Sub-lists can be named (e.g. q1,
q2) for readability, but this is not required.
Single-choice only - all questions have a single correct answer:
sportQuiz <- list(
q1 = list(
question = "Which city hosted the 2024 Summer Olympics?",
options = c("Athens", "London", "Paris", "Tokyo"),
answer = 3
),
q2 = list(
question = "Which sport was added to the Olympics at the 2024 Paris Games?",
options = c("Skateboarding", "Breakdance", "Surfing", "Sport Climbing"),
answer = 2
),
q3 = list(
question = "At the 1900 Olympics in Paris, one sport was featured that has never returned to the Games since. Which one?",
options = c("Live pigeon shooting", "Underwater archery", "Synchronized sneezing",
"Backwards horse vaulting"),
answer = 1
)
)Mixed with tips - some questions include a custom tip text:
sportQuizWithTips <- list(
q1 = list(
question = "Which city hosted the 2024 Summer Olympics?",
options = c("Athens", "London", "Paris", "Tokyo"),
answer = 3,
tip = "They speak French there."
),
q2 = list(
question = "Which sport was added to the Olympics at the 2024 Paris Games?",
options = c("Skateboarding", "Breakdance", "Surfing", "Sport Climbing"),
answer = 2
# no tip for this question - the tip button won't appear here
)
)Multiple-choice - if any question
has multiple correct answers (length(answer) > 1), all
questions automatically use checkboxes:
scienceQuiz <- list(
q1 = list(
question = "Which of these are noble gases?",
options = c("Helium", "Oxygen", "Neon", "Iron"),
answer = c(1, 3)
),
q2 = list(
question = "Which planet is closest to the Sun?",
options = c("Venus", "Mercury", "Mars"),
answer = 2 # single correct answer, but rendered as checkbox
)
)Validate the entire question list with
checkMultiQuestions():
checkMultiQuestions(sportQuiz) # returns invisibly if valid
# Common mistake: flat list instead of list of lists
bad <- list(question = "Q1?", options = c("A", "B"), answer = 1,
question = "Q2?", options = c("C", "D"), answer = 2)
checkMultiQuestions(bad)
#> Error: `x` looks like a single question, not a list of questions.
#> Wrap it in an outer list: list(q1 = list(question = ..., options = ...,
#> answer = ...))
#> See ?checkMultiQuestions or ?multiQuestions for examples.fillBlanks)Define a cloze text with $$!answer!$$ markers for blank
fields. Optionally add distractor options:
rQuiz <- list(
cloze = "R is a $$!programming!$$ language for $$!statistical computing!$$.",
addOptions = c("natural", "colloquial", "movies")
)Normal text wraps automatically across lines. For code formatting,
use <pre> tags — but note that inside
<pre>, you need <br> tags for
explicit line breaks:
codeQuiz <- list(
cloze = "<pre>x $$!<-!$$ c(1, 2, 3)
$$!mean!$$(x)</pre>",
addOptions = c("=", "->", "median", "sum")
)Validate with checkFillBlanks():
checkFillBlanks(rQuiz) # returns invisibly if valid
# Common mistake: misspelled element name
bad <- list(clozed = "R is a $$!programming!$$ language.")
checkFillBlanks(bad)
#> Error: Unknown element(s) in `x`: 'clozed'.
#> Allowed elements are: 'cloze' (required) and 'addOptions' (optional).
#> See ?checkFillBlanks or ?fillBlanks for examples.Once your data is defined, pass it to the quiz function:
myQuestionSC <- list(
question = "Which city hosted the 2024 Summer Olympics?", # the question text
options = c("Berlin", "London", "Paris", "Madrid"), # answer options
answer = 3, # index of the correct answer (Paris)
tip = "Think about the Eiffel Tower." # optional tip text
)
singleQuestion(
x = myQuestionSC,
title = "Geography Quiz",
showTipButton = TRUE
)All three quiz functions share a common set of parameters:
| Parameter | Description | Default |
|---|---|---|
title |
Quiz title text | NULL / "Quiz" |
language |
Language for UI text ("en",
"de", "fr", "es") |
"en" |
shuffle |
Randomize option/question order | FALSE |
showTipButton |
Show a hint button | FALSE |
showSolutionButton |
Show a reveal-solution button | TRUE |
Tips: Set showTipButton = TRUE to
display a tip button. The behavior depends on the quiz type and
mode:
singleQuestion and
multiQuestions): Tips are only shown if you
provide a custom tip text as the 4th element of the question list
(tip = "..."). Questions without a tip simply don’t show
the button.fillBlanks): The
tip shows all available answer options (correct answers + distractors
from addOptions).Submit & retry (MC mode in
singleQuestion): After clicking Submit, a feedback
message shows how many correct and wrong selections were made. The
Submit button is disabled until the user changes their selection (adds
or removes an option), then it can be clicked again. This allows
iterative learning — adjust your answer and retry without reloading the
page.
Shuffling: Set shuffle = TRUE to
randomize order:
singleQuestion: The answer options are
displayed in a random order each time the widget is rendered (e.g. when
the page is loaded or refreshed).multiQuestions: The question order is
randomized on each render, and also each time the user clicks “Try
again” on the results page.| Parameter | Description | Default |
|---|---|---|
width |
Widget width (CSS value) | "100%" |
height |
Widget height (CSS value) | "500px" |
scroll |
Fixed height with scrollbar (useful for slides) | FALSE |
center |
Center the widget horizontally | TRUE |
All visual aspects are customizable — colors, fonts, sizes. Common parameters include:
| Parameter | Description | Default |
|---|---|---|
fontFamily |
Font stack | "'Helvetica Neue', ..." |
fontSize |
Base font size in px | 16 |
titleCol / titleBg |
Title text & background color | "#FFF" / "#5F5F5F" |
questionCol / questionBg |
Question (or description in fillBlanks) text & background color. Also serves as feedback area (green/red). | varies |
See the function documentation (?singleQuestion,
?multiQuestions, ?fillBlanks) for the full
list of design parameters.
If you use multiple quizzes in the same document and want a
consistent look, create a reusable theme with rquizTheme()
instead of repeating color settings:
# Define a dark theme once
dark <- rquizTheme(
fontFamily = "Georgia, serif",
fontSize = 18,
titleCol = "#E0E0E0", titleBg = "#1A1A2E",
questionCol = "#FFFFFF", questionBg = "#16213E",
mainCol = "#E0E0E0", mainBg = "#1A1B2E",
optionBg = "#252540",
navButtonCol = "#FFFFFF", navButtonBg = "#E94560",
tipButtonCol = "#E0E0E0", tipButtonBg = "#2C2C3E",
solutionButtonCol = "#E0E0E0", solutionButtonBg = "#2C2C3E"
)
# Apply to any quiz - all share the same design:
singleQuestion(x = myQuestionSC, theme = dark, title = "Quiz 1")
multiQuestions(x = scienceQuiz, theme = dark, title = "Quiz 2")
fillBlanks(x = rQuiz, theme = dark, title = "Quiz 3")The theme provides defaults that are overridden by any explicitly passed arguments:
# Use the dark theme but override the title background:
singleQuestion(x = myQuestionSC, theme = dark, title = "Quiz 1",
titleBg = "#FF0000")See ?rquizTheme for all available theme parameters,
including quiz-specific ones like optionLabelBg
(singleQuestion), navButtonBg (multiQuestions), or
descriptFontSize (fillBlanks).
Since quiz content is rendered as HTML, you can use HTML tags to
format text within questions, options, tips, and cloze text. Standard
Markdown formatting (*italic*, **bold**) does
not work inside quiz strings — use HTML tags
instead.
Use <em> for italics (e.g. species names) and
<strong> for bold:
Use <code> for inline code formatting:
Use <span> with inline CSS for colored or styled
text:
Use <pre> tags for monospace code blocks in
fillBlanks(). Inside <pre>, line breaks
require the <br> tag (normal line breaks are
ignored):
Set language to change all UI text (buttons, messages)
automatically:
# German
singleQuestion(x = myQuestionSC, language = "de")
# French
multiQuestions(x = sportQuiz, language = "fr")
# Spanish
fillBlanks(x = rQuiz, language = "es")Currently supported: English ("en"), German
("de"), French ("fr"), Spanish
("es"). To request additional languages, please open an
issue on GitHub.
Simply include quiz code in a code chunk. The widget renders as part of the HTML output:
```{r}
library(rquiz)
singleQuestion(
x = list(
question = "What is 2+2?",
options = c("3", "4", "5"),
answer = 2
),
title = "Math"
)
```For HTML slide presentations (e.g. revealJS,
xaringan), use scroll = TRUE to add a scrollbar when the
quiz exceeds the available slide space:
Use the provided output/render functions:
library(shiny)
library(rquiz)
ui <- fluidPage(
h2("My Quiz App"),
singleQuestionOutput("quiz")
)
server <- function(input, output) {
output$quiz <- renderSingleQuestion({
singleQuestion(
x = list(
question = "What is 2+2?",
options = c("3", "4", "5"),
answer = 2
)
)
})
}
shinyApp(ui, server)Each quiz type has its own pair: singleQuestionOutput()
/ renderSingleQuestion(),
multiQuestionsOutput() /
renderMultiQuestions(), fillBlanksOutput() /
renderFillBlanks().