HomeFeaturesLooking for GamesMarketplaceForumWikiContact
Sign up
Log in
Sign up
Log in

Ruleset Skins

A skin restyles Realm VTT for the campaigns that use your ruleset: windows and their headers, chat cards, the chat input, the drawer and hotbar, scene tools, character sheets, tag colors, chat macro and sheet buttons, the loading and pause screens, and the logo shown when no scene is open. A skin can bring its own colors, tiling textures (paper, leather, stone), fonts and images.

Skins belong to a ruleset. Once the ruleset is published, anyone can use its skins in any campaign. A ruleset can ship several skins and pick one as the default players see.

A skin is data only. Realm checks every value in it and builds the styles itself, so a skin can't run scripts or load files from anywhere but its own folder.

How players choose a skin

Each player picks a skin per ruleset. In Settings, under Appearance, Skin for all (ruleset name) campaigns applies to every campaign they play on that ruleset:

  • Ruleset's default uses the skin the ruleset author picked.
  • None (use Theme) keeps their regular Realm theme.
  • Or pick any other skin they have access to.

Only that player sees their choice. While a skin is on, it replaces their Theme. When a GM creates a campaign on a ruleset with a default skin, the welcome window also asks whether to use it or keep their Realm theme; that answer is the same per-ruleset setting.

Making a skin

A skin is a folder with a skin.json manifest and the images and fonts it uses:

my-skin/
  skin.json
  frames/paper.png
  frames/leather.png
  fonts/MyFont-Regular.woff2
  fonts/MyFont-Bold.woff2

File paths in skin.json are relative to this folder. Images can be png, jpg, webp or avif; fonts can be woff2, woff, ttf or otf. Other files in the folder, such as a font license, are skipped when you upload. Only use art and fonts you have the rights to share: a published skin is public.

A minimal skin:

{
  "name": "Parchment",
  "colorScheme": "dark",
  "fonts": [{ "family": "MyFont", "src": "fonts/MyFont-Regular.woff2" }],
  "fontRoles": { "heading": "MyFont", "windowTitle": "MyFont" },
  "frames": {
    "chatCard": {
      "texture": { "src": "frames/paper.png", "scale": 2 },
      "textColor": "#191813",
      "scheme": "light",
      "border": "#6c6964",
      "radius": 5
    },
    "sheetCorner": { "hidden": true }
  }
}

Uploading a skin

From the ruleset editor

Open your ruleset in the Ruleset Editor and choose the Skins tab:

  • Upload skin folder reads a folder with skin.json, checks it, and uploads it. Use a new id to add a skin, or an existing id to replace that skin.
  • Preview applies a skin while you stay in the editor.
  • Make default sets the skin players see unless they pick another one.
  • Delete removes a skin. Campaigns that used it go back to their normal look.

Only the ruleset's owner can upload, replace or delete its skins.

With the Ruleset Compiler

If you keep your ruleset in a folder and push it with the Ruleset Compiler, put each skin in a skins/ folder next to ruleset.config.json. The folder name is the skin's id (lowercase letters, digits and dashes):

my-ruleset/
  ruleset.config.json
  skins/
    parchment/
      skin.json
      frames/paper.png

Every push uploads the skins after the ruleset, with your login or with a deploy token from CI. The build fails if a skin.json is missing, is not valid JSON, or points at a file that isn't in its folder. Pass --skip-skins to push only the ruleset, and --dry-run to see which skins would upload.

Setting the default skin

Set the default in ruleset.config.json with the skin's id:

{ "settings": { "skin": "parchment" } }

or use Make default in the editor's Skins tab.

skin.json reference

Colors can be hex (#5e0000), rgb(), rgba() or transparent. Sizes are in CSS pixels.

Top-level fields

Field

What it does

name

Display name (required)

colorScheme

"light" or "dark" (required)

description

Short description

palette

colors: 10-shade color ramps for primary, secondary, tertiary, dark, gray, red, green, blue and the other Mantine colors; primaryShade (0 to 9); other: window and chat card colors

tokens

Sheet colors: accent, accent-glow, accent-subtle, bg, surface, surface-elev, text, text-dim, text-muted, border, border-strong, divider, danger, success

fonts

Up to 12 fonts: { "family", "src", "weight", "style" }

fontRoles

Which font each role uses: body, heading, windowTitle, modalTitle (dialog titles; defaults to windowTitle), chat, whisper

radius

Default corner radius for inputs and buttons

frames

How each part of the screen is drawn (below)

recordTypes

Frames for one record type's windows (below)

tags

Tag colors by tag text (below)

macroButtons

Chat macro button looks (below)

sheetButtons

Looks for sheet buttons the ruleset tags with skin="..." (below)

scrollbar

{ "thumb", "thumbHover" } colors

Frames

frames maps a part of the screen (a slot) to how it is drawn. Leave a slot out to keep the normal look.

Area

Slots

Windows

window, windowHeader, recordWindowHeader, windowTitle, windowContent, recordWindowContent, recordBody, tabList, tab, tabActive, modal, panel, tooltip

Chat

chatCard, chatCardWhisper, chatCardPrivate, chatInput

Drawer and app bar

drawer, drawerTab, drawerTabActive, appBar

Scene

toolbar, sceneButton, sceneButtonActive, sceneTab, sceneTabActive, sceneDecal

Hotbar

hotbar, hotbarTabs, hotbarTab, hotbarTabActive, hotbarRow, hotbarDivider, hotbarSlot, hotbarSlotFilled

Combat tracker

ctRow, ctRowActive

Sheets

sheetSection, attributeCard, sheetCorner

Loading and pause

loadingCard, loadingLogo, pauseCard, pauseIcon

Buttons

button, iconButton

A frame can set:

Field

What it does

background

Solid color

texture

{ "src", "scale" }: a repeating tile. scale 2 (the default) draws it at half its pixel size; use up to 8 for high-resolution art

art

{ "src" }: replaces what the element draws with this image, centered and scaled to fit. Use it for the empty-scene logo (sceneDecal), the loading logo or the pause icon, with size for the box

border, borderWidth

Border color, and width (up to 8)

radius

Corner radius (up to 32)

shadow

Drop shadow color

size, iconSize

Square size of a control or art box (12 to 320), and the icon size inside it

gap, padding

Space between children (up to 32), and the inset as [top, right, bottom, left]

textColor, fontWeight, uppercase

Text color (it also recolors the Realm logo in sceneDecal and loadingLogo), weight (100 to 900), and forced uppercase

hidden

Hide the element, such as the corner marks on sheets: "sheetCorner": { "hidden": true }

hover

A frame layered on while the pointer is over the element

scheme, tokens, darkShades, grayShades

Recolor everything inside the frame. A light parchment area in a dark skin sets "scheme": "light", sheet tokens, and light darkShades and grayShades ramps (10 colors each) so inputs and text read well

Record types

recordTypes styles one record type's windows, using the ruleset's record type names. For example, crimson headers for characters and NPCs but not items:

"recordTypes": {
  "characters": { "frames": { "windowHeader": { "background": "#5e0000" } } },
  "npcs": { "frames": { "windowHeader": { "background": "#5e0000" } } }
}

The slots you can use here are window, windowHeader, tabList, tab, tabActive and recordBody.

Tags

Tags in chat and on sheets (traits, ranges, rarity) can be colored by their text, so your ruleset doesn't have to send any colors. Every tag gets base plus the default variant, and match picks a variant by the tag's text, in lowercase. A * at the start or end matches any text there. A variant the ruleset sends on the tag wins over matching, and a color the ruleset sets on the tag wins over everything.

"tags": {
  "base": { "radius": 2, "textColor": "#ffffff", "fontWeight": 600, "uppercase": true },
  "variants": {
    "trait": { "background": "#786452" },
    "action": { "background": "#5e0000" },
    "uncommon": { "background": "#98513d" },
    "rare": { "background": "#002664" }
  },
  "default": "trait",
  "match": {
    "action": ["attack", "* save", "concentrate"],
    "uncommon": ["uncommon"],
    "rare": ["rare"]
  }
}

Macro buttons

These style the macro buttons in chat messages (the buttons a ruleset adds with a macro fence, such as Roll Damage or Apply Half Damage).

Field

What it does

style

The base frame for every macro button

layout

"stacked" puts the icon above a small uppercase label

row

true lays a message's buttons out as one row of equal-width buttons

kinds.effect

The look for macros that apply an effect or condition, detected from the macro's code

variants and match

Named looks, matched by macro name: lowercase, spaces as underscores, * at the start or end. Later entries in match win

buttons

One macro's icon and a shorter label, keyed by macro name

Icons are Realm icon names: Tabler icons (IconHeartBroken), game-icons (GiShield), dice (d20), or damage types (fire, cold, acid). A variant's icon applies to matched buttons that don't have their own.

"macroButtons": {
  "style": { "background": "#d8d5ca", "border": "#7a7971", "textColor": "#191813" },
  "layout": "stacked",
  "row": true,
  "kinds": { "effect": { "background": "#5e0000", "textColor": "#ffffff", "icon": "IconSparkles" } },
  "variants": {
    "attack": { "background": "#3a2e26", "textColor": "#f4ecd8", "icon": "d20" },
    "damage": { "background": "#f1e6cf", "textColor": "#5e0000", "icon": "IconDice5" }
  },
  "match": { "damage": ["*_damage"], "attack": ["roll_attack*"] },
  "buttons": {
    "Roll_Fire_Damage": { "icon": "fire" },
    "Apply_Half_Damage": { "icon": "IconHeartMinus", "label": "Half" }
  }
}

Sheet buttons

These style buttons on your ruleset's sheets, such as Damage, Cast or Use. Tag a <button> or <iconbutton> in your sheet HTML with a skin attribute naming a look (lowercase letters, digits and dashes):

<button label="Damage" skin="damage" onclick="rollDamage(false)"></button>
<button label="CAST" skin="use" onclick="onCastSpell()"></button>
<iconbutton skin="use" options='[{"label": "Send to Chat", "icon": "IconSend", "value": "send"}]'></iconbutton>

In skin.json, sheetButtons.base applies to every tagged button and sheetButtons.variants holds one frame per name. Without a skin, or with a skin that doesn't name that variant, the button keeps its normal look.

"sheetButtons": {
  "base": { "textColor": "#ffffff", "border": "rgba(0, 0, 0, 0.5)", "radius": 3, "fontWeight": 500 },
  "variants": {
    "attack": { "background": "#171f69", "hover": { "background": "#242f8f" } },
    "use": { "background": "#171f69", "hover": { "background": "#242f8f" } },
    "damage": { "background": "#5e0000", "hover": { "background": "#7a1010" } }
  }
}

The icon and label follow the button's textColor.

Limits

  • Up to 48 files per skin, 4 MB per file and 16 MB in total; skin.json up to 200 KB.
  • Up to 12 fonts, 32 record types, 24 tag variants, 400 tag patterns, 64 macro buttons and 24 sheet button variants. Macro labels up to 24 characters.
  • Each upload creates a new version with fresh file URLs, so players never see a mix of old and new art.

Tips

  • Start from the default skin of a ruleset you like, and change one area at a time.
  • Use the editor's Preview to check a skin before you make it the default.
  • Textures should tile seamlessly. Export them at twice the size you want them drawn and keep scale at 2.
  • If a skin doesn't apply, the browser console shows what Realm rejected in its skin.json.
Wiki/Developers/Ruleset Skins
Documentation
About
Looking for Games - Free and Paid Games on Realm VTT
Developers
Ruleset Editor and API
Campaign Variables
Advanced Ruleset Creation
Advanced Ruleset Sheet CSS
Ruleset Skins
Help
Troubleshooting
Tutorials
Getting Started
Level Up: Advanced 5th Edition (A5e) Ruleset User Guide
New User Tour
Modules
Creating and Editing Scenes
VTT Controls
D&D 5th Edition (2024) Ruleset User Guide
Interactive World Maps: A Guide to Atlas Mode
DevelopersUpdated 11/9/2026 · 9 min read

Ruleset Skins

Was this helpful?