mascot puts a small character, drawn in code, into your
Shiny app or Quarto page. The character shows a mood with a status line
beside it and wiggles into its new face when the mood changes. You
control it from R with plain arguments; no AI service is involved. The
package is built for good-practice (GxP) work: a mascot
sits beside your ordinary field errors and results, and does not replace
them.
What you get:
"idle", "thinking",
"happy", "worried" and
"celebrating").bslib colour toggle or Quarto’s theme mode.bslib::bs_theme()
(which parts it colours is experimental).mascot_studio()
(experimental) lets you try every function live and copy the R
code.Install the released version of mascot from CRAN:
Or install the development version from GitHub with
pak:
The fastest way to see what mascot can do is the studio,
a small app that runs on your computer and needs no internet. It is
experimental, so its looks may change, and it uses the optional
bslib and knitr packages.
mascot_studio()
opens the studio. Pick a character, pick a function from the list,
change its arguments and watch the mascot react. The R code to copy sits
under the picture and always matches what you see.mascot_studio(view = "theme") (or
mascot_example("theme")) opens Theme a
mascot. Choose one character, then try a ready-made palette,
your own colours for light and dark mode, or the colours of your app’s
bslib theme. Its Check tab shows every
mood in light and dark and warns about low contrast, and the code to
copy is the shortest mascot_theme() call that gives the
same look.mascot_studio(view = "examples") lists the bundled
example apps. You can also open the theme page from the studio’s
More menu.mascot_studio(fn = "mascot_theme", character = "sela").Suppose you have a batch review page where Tessa, the default
character, waits in the corner until someone clicks Finish
review, then smiles and confirms that every row passed. Put
this code in an app.R file and run it from an interactive R
session:
library(shiny)
library(mascot)
ui <- fluidPage(
h1("Batch review"),
actionButton("finish", "Finish review"),
mascot("helper", status = "Ready to review")
)
server <- function(input, output, session) {
observeEvent(input$finish, {
update_mascot(
"helper",
mood = "happy",
status = "Review complete",
bubble = mascot_bubble("All rows checked.", duration = 5)
)
})
}
shinyApp(ui, server)How each line works:
library(shiny) and library(mascot) load
Shiny and the package.mascot()
adds Tessa in the bottom-right corner with the id "helper"
and "Ready to review" beside her.observeEvent(input$finish, { ... }) waits for your user
to click Finish review.update_mascot()
updates "helper" in place using the same id.mood = "happy" and
status = "Review complete" change Tessa’s face and status
text together.mascot_bubble()
shows a speech bubble above Tessa that closes after five seconds
(duration = 5).You can try the bundled Shiny apps before writing your own code.
mascot_examples()
lists the seven bundled example apps in teaching order.mascot_example()
runs an example by name, or returns the path to its app.R
with run = FALSE.mascot_studio()
(experimental) opens an app where you can click through every function
and copy the code.mascot_examples()
#> name title
#> 1 studio Mascot studio
#> 2 form-check Check a form
#> 3 long-task Show task progress
#> 4 modules Work in modules
#> 5 theme Theme a mascot
#> 6 golem Work in golem
#> 7 rhino Use a rhino-shaped structure
#> description
#> 1 Every function and argument, live, with explanations and the code to copy.
#> 2 A worried helper names invalid fields and becomes happy when both pass.
#> 3 Thinking during a slow step, then success or a lasting error bubble. Requires promises and later.
#> 4 Two module helpers and a parent-owned floating helper.
#> 5 A palette, your own or your brand's colours, the code to copy and every mood checked. Requires bslib.
#> 6 Function-built UI, golem runtime options and two nested review modules. Requires golem.
#> 7 Plain Shiny view modules with two nested reviews; no rhino or box needed.mascot()
in the UI and update_mascot()
in the server, naming all other arguments.NULL leaves a setting unchanged: in
update_mascot(), NULL keeps the current value
on the page, while calling a helper such as mascot_bubble()
with no arguments resets it.update_mascot() warn and leave the mascot
untouched instead of stopping the app.size = 180, or use mascot_size()
when you want extra options.When a form has missing entries or a report takes 20 seconds to build, update the mood and status line together so people using your app can see what is happening.
"idle",
"thinking", "happy", "worried"
and "celebrating"), listed by mascot_moods().mascot_mood()
in mascot()
and update_mascot().status = NULL in mascot() to use the
mood’s default status line, or pass your own text with mascot_status()."thinking" mood first, do the
work, then update the mood when it finishes ("long-task" in
mascot_example()
shows how).mascot_character(),
list them with mascot_characters(),
and see their families and attachment points with mascot_families()
and mascot_anchors().mascot_characters()
#> id name
#> 1 tessa Tessa
#> 2 sela Sela
#> 3 knurl Knurl
#> 4 neer Neer
#> description
#> 1 A calm teal hexagonal maker with a silver ring, a bow and a chest badge that shows her mood.
#> 2 A reassuring lilac Hexonian with time blocks; suits scheduling and resource planning.
#> 3 A single-wheeled repair bot with a clamp arm and screen visor; suits job queues and diagnostics.
#> 4 A blue wave crest with a scallop clip.
#> family is_default built_in lifecycle
#> 1 hexonians TRUE TRUE stable
#> 2 hexonians FALSE TRUE stable
#> 3 visorfolk FALSE TRUE experimental
#> 4 elementals FALSE TRUE experimental
#> palettes
#> 1 night_teal, sea_green, forest, spring, peach, coral_navy, desert_coral, rose_soft, lilac_soft, mist_navy
#> 2 lilac_violet, orchid, coral_navy, azure, peach_ink, lilac_soft, mist_navy, desert_clay, forest, apricot_navy, blush_plum, lilac_dusk, sea_green
#> 3 original, orbit, carbon, cobalt_droid, crimson
#> 4 original, lagoon, glacier, coral_reef, night_bay, monsoon
mascot_moods("tessa")
#> name description
#> 1 idle At rest; the starting mood.
#> 2 thinking Working on something, such as a long calculation.
#> 3 happy Something went well.
#> 4 worried Something needs attention, such as an invalid field.
#> 5 celebrating A big success, such as a finished task.
#> status shared fallback
#> 1 Resting, ready for the next piece. TRUE <NA>
#> 2 Working out how the parts fit. TRUE <NA>
#> 3 That came together nicely. TRUE <NA>
#> 4 One part needs a closer look. TRUE <NA>
#> 5 Finished, and built to last. TRUE <NA>update_mascot("helper", mood = "worried", status = "2 fields need attention")
update_mascot("helper", mood = "happy", status = mascot_status())You can read and check a character file with mascot_read_character()
or mascot_check_character()
(both experimental) and pass the result to
mascot_character(), mascot(),
update_mascot() or mascot_preview().
Saving and registering characters and palettes are planned for a later
release. See Write
your own character for details.
While the status line shows the current state, a speech bubble announces a specific event, such as three skipped rows in an uploaded file.
mascot_bubble()
to bubble in mascot()
or update_mascot().duration = 5) and
shows a close button by default.duration = Inf for a message your user must act on
so it stays until closed or cleared with
mascot_bubble().htmltools::HTML() or
self-contained htmltools tags.When someone saves a filter or switches tabs, a one-off gesture plays a quick nod or wave without changing the character’s mood.
gesture in update_mascot(),
using a name like "nod" or mascot_gesture()."nod" to acknowledge an action and
"wave" to greet, listed by mascot_gestures().mascot_motion_settings()
can skip the movement while keeping the rest of the update.mascot_gestures("tessa")
#> name description
#> 1 nod A short nod, such as to acknowledge an action.
#> 2 wave A wave, such as a greeting; characters without arms rock instead.
#> 3 rock Rock gently.
#> 4 shimmer A glint runs once round the ring.
#> fallback frames
#> 1 <NA> 18
#> 2 rock 30
#> 3 <NA> 18
#> 4 <NA> 45A mascot can float in a window corner as an app-wide helper or sit inline inside a card right next to a form.
mascot()
draws at 160 pixels in the "bottom-right"
corner with a minimise button (minimise = TRUE).size to a pixel number (at least 24),
a percentage like "20%", or mascot_size().small_screen = "auto" shrinks
floating mascots to at most 96 pixels while leaving inline
mascots unchanged.placement to "inline", a corner name
like "top-left", or mascot_placement().input$<id>_minimised in Shiny.drag = "snap" for corner snapping or
drag = "free" (experimental) for free dragging. In Shiny,
each drop updates input$<id>_position, in either
mode.When your app has its own brand palette or a dark mode switch, you
can match the character’s colours through theme.
theme = NULL in mascot()
to keep the character’s own colours (or pass "original" in
update_mascot()
to restore them).mascot_palettes(),
such as theme = "coral_navy" for Tessa.mascot_colour_roles()
to see a character’s parts and which leader roles they follow when you
set only a few colours.light and dark lists in mascot_theme()
for separate light and dark colours, or css for browser CSS
variables (experimental).bslib::bs_theme() to fill the character’s
primary and secondary brand roles (experimental role mapping).mode = "auto" follows
bslib::input_dark_mode() or Quarto’s dark mode, and stays
light when the page gives no signal.mascot_studio(view = "theme").mascot_palettes("tessa")
#> name
#> 1 night_teal
#> 2 sea_green
#> 3 forest
#> 4 spring
#> 5 peach
#> 6 coral_navy
#> 7 desert_coral
#> 8 rose_soft
#> 9 lilac_soft
#> 10 mist_navy
#> description
#> 1 The character's own colours, with no palette applied.
#> 2 Sea-green face, silver ring, dark navy hair and a coral bow.
#> 3 Forest-green face, gold ring and bow, deep pine hair.
#> 4 Light spring-green face, gold ring, forest hair and a pink bow.
#> 5 Peach face, amber ring, plum-brown hair and a red-coral bow.
#> 6 Pale peach face, coral ring, navy hair and a teal bow.
#> 7 Sand face, coral ring, terracotta hair and a gold bow.
#> 8 Soft rose face, rose-gold ring, berry-brown hair and a pale gold bow.
#> 9 Soft lilac face, mauve ring, dusky violet hair and a pale gold bow.
#> 10 Pale blue-grey face, steel-blue ring, navy hair and a gold bow.
#> source
#> 1 native
#> 2 character
#> 3 character
#> 4 character
#> 5 character
#> 6 character
#> 7 character
#> 8 character
#> 9 character
#> 10 characterOn a busy review dashboard where extra movement would distract people, you can quieten or stop animation while keeping every mood and message working.
motion = FALSE to mascot()
or update_mascot()
to stop all movement at once.mascot_motion_settings()
to switch idle, mood_change,
gestures, swap, minimising,
restoring and messages on or off
individually.NULL follows others
(TRUE by default), so others = FALSE with
gestures = TRUE allows only gestures.TRUE.mascot_gesture()
triggers one-off movements when allowed.When you split a larger Shiny app into modules, including
golem and rhino apps, each module can own and
update its own mascot.
mascot()
with a namespaced id such as mascot(ns("helper"))."helper" to update_mascot()
inside shiny::moduleServer().input$helper_click,
input$helper_menu, input$helper_position and
input$helper_minimised directly from that module’s
input.mascot_example()
with "modules", "golem" or
"rhino" to see full working apps.review_ui <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
shiny::actionButton(ns("finish"), "Finish review"),
mascot(ns("helper"), placement = "inline")
)
}
review_server <- function(id) {
shiny::moduleServer(id, function(input, output, session) {
shiny::observeEvent(input$finish, {
update_mascot("helper", mood = "happy", status = "Review complete")
})
})
}You can also place mascot()
in a rendered Quarto document with no Shiny server to show a fixed mood
and status line.
placement = "inline" (or mascot_placement())
to sit in the page flow, and give each mascot a unique
id..mascot-bubble-region { display: none; } in
CSS.embed-resources: true).shortcode is planned for 0.2.0.Put this in an R chunk of your .qmd file:
Before wiring a character or colour theme into your app, you can inspect every mood and size on one offline page or try controls live in the studio.
mascot_preview()
(experimental) builds a static offline page showing every mood in light
and dark mode across your chosen size and 160, 96 and 24 pixels.motion = FALSE
by default so you can compare faces side by side.mascot_preview() to inspect
attr(page, "contrast") for any custom colours below 3:1
contrast, and print page to view it.mascot_studio()
(experimental) opens an offline app where you can test every function,
check themes with view = "theme", and copy R code.mascot_examples()
and mascot_example()
to browse or open the seven bundled example apps.When you call update_mascot()
from the server, you can leave settings alone, replace them, or reset
them to their defaults.
NULL in
update_mascot() keeps its current value, whereas
NULL in mascot()
means none or the default.NULL argument replaces that whole setting at
once (for example, size = 200 also resets
small_screen to "auto").update_mascot() is invalid, R warns and
ignores the whole call so the mascot never updates halfway.shiny::renderUI() mascot appears
on screen are queued in order and applied as soon as it mounts.| Setting | Reset sent to update_mascot() |
Result |
|---|---|---|
| Character | character =mascot_character() |
Switches back to "tessa" |
| Mood | mood =mascot_mood() |
Returns to "idle" |
| Size | size =mascot_size() |
Resets to 160 pixels
(small_screen = "auto") |
| Custom status | status =mascot_status() |
Follows the mood again |
| Bubble | bubble =mascot_bubble() |
Removes the speech bubble |
| Theme | theme =mascot_theme()
(or "original") |
Restores the character’s own colours |
| Click | click =mascot_action()
(or mascot_popup_menu()) |
Makes the mascot a picture again |
| Placement | placement =mascot_placement() |
Floats in "bottom-right" ("inline" for
inline) |
| Motion | motion =mascot_motion_settings() |
Allows movement again |
mascot_studio())
for visual guides to mascot(),
update_mascot(),
mascot_theme()
and every other export.?mascot,
?"mascot-package" and ?mascot_styling in R)
for all argument details, condition classes and CSS variables.mascot_examples()
and mascot_example()
to copy working apps for form checks, slow tasks, modules,
golem and rhino.mascot_read_character()
and mascot_check_character()
read and check a character file.