Character Vault
Any Concept / Any System
Compendium
Your System Come To Life
Roll20 for Android
Streamlined for your Tablet
Roll20 for iPad
Streamlined for your Tablet

Personal tools

Difference between revisions of "CSS Wizardry"

From Roll20 Wiki

Jump to: navigation, search
m
m (Custom Progress Bar)
 
(125 intermediate revisions by 15 users not shown)
Line 1: Line 1:
''Main Article:'' '''[[Building Character Sheets]]'''
+
{{revdate}}{{BCS}}
  
 
Learning CSS and HTML can be daunting. And yet, if you want to [[Building Character Sheets|create a character sheet]] for Roll20, both are required.
 
Learning CSS and HTML can be daunting. And yet, if you want to [[Building Character Sheets|create a character sheet]] for Roll20, both are required.
 +
{{NavSheetDoc}}
 +
This page gives a number of examples for creative way to use CSS within the limits of the Roll20 Character Sheet Framework.
  
This page gives a number of examples for creative way to leverage the character sheet system. Most of these tips involve CSS and were pulled from the <span class="plainlinks">[https://app.roll20.net/forum/post/882997/css-wizardry/?pagenum=1 CSS Wizardry]</span> thread in the Character Sheets forum, but some tips don't necessarily involve CSS at all.
+
This article and some of the tips originate from the old <span class="plainlinks">[https://app.roll20.net/forum/post/882997/css-wizardry/?pagenum=1 CSS Wizardry]</span>-thread in the Character Sheets forum, but have over the years expanded beyond that as the framework matured with changes like [[CSE]] and newer CSS features like [[Designing_Character_Sheet_Layout#CSS_Flexbox|Flexbox]] or [[Designing_Character_Sheet_Layout#CSS_Grid|CSS Grid]]  Some tips don't necessarily involve CSS at all, but are quirks in the sheet system.
 +
 
 +
{{cleanup-msg| Not all examples are yet converted to work with newer [[CSE]]-sheets, [[Legacy Sheet]], so need adjustments to . See [[CSE#How to update sheet to new|Character Sheet Enhancement - Update]] for more. |April 2021}}
  
 
Many of these tips also include links to a live demo on [http://jsfiddle.net JSFiddle], so you can see them in action.
 
Many of these tips also include links to a live demo on [http://jsfiddle.net JSFiddle], so you can see them in action.
 +
 +
 +
[https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity CSS Specificity]
 +
 +
'''Other pages related to CSS & Design for Roll20 character sheets''':
 +
 +
* [[Designing Character Sheet Layout|Designing '''Character Sheet Layout''']] - tips on how to best design the broad strokes of a character sheet
 +
* [[Image use in character sheets|'''Image use''' in character sheets]] - How to include images on your character sheets
 +
* [[JQuery]] - can be used to add & remove classes from elements
 +
* [[Character Sheet Development/Dark Mode]]
 +
* [[Character Sheet Development/Best_Practice|Sheet Design Best Practices]]
 +
<br>
 +
=Elements=
 +
Tips for styling many of the basic elements of a sheet
 +
 +
==Number/Text Input==
 +
 +
=== textarea ===
 +
by [https://app.roll20.net/forum/permalink/10468235/ Alan S.]
 +
 +
For some reason, the default for the input elements is <code>box-sizing: border-box</code>, while the default for the <code>textarea</code> was <code>box-sizing: content-box</code>. This leads the default textarea to more easily take more space than it should, overflowing it parent element, while inputs more rarely do. See more: [https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing box-sizing]
 +
 +
Explicitly adding a box-sizing to my textarea css, seen below, fixed the problem.
 +
 +
<pre data-language="css">.charsheet textarea.textbox {
 +
    resize: none;
 +
    box-sizing: border-box;
 +
    height: 231px;
 +
}</pre>
 +
 +
=== Content-scaled Inputs ===
 +
By stacking your input with an attribute-backed span that defines the dimensions of the parent, you can create inputs that resize themselves to fit their text.
 +
 +
The html baseline is a simple parent container with your input and span inside:
 +
<pre data-language="html">
 +
&#60;div class="autoExpand"&#62;
 +
&#60;input type="text" name="attr_expandText"&#62;
 +
&#60;span name="attr_expandText"&#62;&#60;/span&#62;
 +
&#60;/div&#62;
 +
</pre>
 +
 +
Here is the CSS with comments about what the properties are doing for you:
 +
<pre data-language="css">.autoExpand {
 +
    overflow: hidden; /* hides overflow that is caused by the span */
 +
    position: relative; /* Allows the input's absolute positioning to be relative to this parent div */
 +
    min-width: 50px; /* Whatever feels good to you, prevents a new span from collapsing to 0. */
 +
    width: fit-content; /* Will fit the width to the contents without going outside itself or collapsing smaller than its content, though we will be using min-width in the case where there's no content. */
 +
    height: 20px; /* Prevent the span from defining the height of the input container which can't be multiline */
 +
}
 +
.autoExpand span {
 +
    visibility: hidden;
 +
    font-size: 1em;
 +
    padding: 0 8px; /* Matching my input's padding so that the widths are correct */
 +
}
 +
.autoExpand input {
 +
    position: absolute; /* removes the input from the DOM flow, allowing the span to exist in the same space */
 +
    width: 100%; /* Inputs match the width of the parent element as defined by the spans */
 +
    font-size: 1em;
 +
}
 +
</pre>
  
 
== Four Ways to Use an Attribute ==
 
== Four Ways to Use an Attribute ==
Line 29: Line 93:
  
 
=== Autocalc Fields ===
 
=== Autocalc Fields ===
 +
''Main Page:'' '''[[Auto-Calc]]'''
 +
 
If a field has the <code>disabled</code> attribute, the user will be unable to modify its value and its value will be treated as a mathematical equation (which can reference other attributes of the character). The result of that formula will be what the user sees. Errors in the formula (for example, <code>@{a} + @{b} + @{c}</code> when attribute b has no value) will result in no output.
 
If a field has the <code>disabled</code> attribute, the user will be unable to modify its value and its value will be treated as a mathematical equation (which can reference other attributes of the character). The result of that formula will be what the user sees. Errors in the formula (for example, <code>@{a} + @{b} + @{c}</code> when attribute b has no value) will result in no output.
  
When using sheet worker scripts, the value of the autocalc field you get from the <code>getAttrs</code> function will be its formula, and you cannot set its value to something else. See [https://github.com/Lithl/lithl-snippets/tree/master/sheetworker-autocalc sheetworker-autocalc] for a utility to resolve autocalc fields to their calculated value in a sheet worker script. {{note|sheetworker-autocalc has not been tested with repeating fields.}}
+
When using [[sheet workers]], the value of the autocalc field you get from the <code>getAttrs</code> function will be its formula, and you cannot set its value to something else. See [https://github.com/Lithl/lithl-snippets/tree/master/sheetworker-autocalc sheetworker-autocalc] for a utility to resolve autocalc fields to their calculated value in a sheet worker script. {{note|sheetworker-autocalc has not been tested with repeating fields.}}
  
 
=== Readonly Fields ===
 
=== Readonly Fields ===
If a field has the <code>readonly</code> attribute, the user will be unable to modify its value, and its default styling will be the same as if it were [[#Autocalc Fields|disabled]]. However, sheet worker scripts will be able to modify its value, and if its value is some kind of equation, it won't be automatically calculated. {{note|If for some reason a readonly field is an equation, sheetworker-autocalc will be able to resolve it to a value just fine.}}
+
If a field has the <code>readonly</code> attribute, the user will be unable to modify its value, and its default styling will be the same as if it were [[#Autocalc Fields|disabled]]. However, sheet worker scripts will be able to modify its value, and if its value is some kind of equation, it won't be automatically calculated. {{note|If for some reason a <code>readonly</code> field is an equation, sheetworker-autocalc will be able to resolve it to a value just fine.}}
  
 
=== Attribute-Backed &lt;span&gt;s ===
 
=== Attribute-Backed &lt;span&gt;s ===
A <code>&lt;span&gt;</code> element can be given an "attr_" name, just like one of the form elements, above. This will cause the span to behave similarly to a readonly field in that the user cannot modify it directly, and sheet worker scripts have no trouble doing so. There are two main differences with an attribute backed span:
+
A <code>&lt;span&gt;</code> element can be given an <code>attr_</code> name, just like one of the form elements, above. This will cause the span to behave similarly to a <code>readonly</code> field in that the user cannot modify it directly, and sheet worker scripts have no trouble doing so. There are two main differences with an attribute backed span:
  
 
# The default styling: the span will look just like the surrounding text
 
# The default styling: the span will look just like the surrounding text
# When using attribute backed spans in a repeating section, you should always make a hidden input version of the attribute as attribute backed spans in repeating sections cannot be called without the full repeating section syntax (e.g. @{repeating_SECTIONNAME_$X_attribute_name}).
+
# When using attribute backed spans in a [[BCS/Repeating Section|repeating section]], you should always make a hidden input version of the attribute as attribute backed spans in repeating sections cannot be called without the full repeating section syntax (e.g. <code>@{repeating_SECTIONNAME_$X_attribute_name}</code>).
  
== Styling Checkboxes and Radio Buttons ==
+
== Checkbox and Radio Input==
 +
 
 +
Checkboxes and radio buttons don't like getting changed much. Instead, it can be easier to use a hidden attribute and present a button to update the attribute.
 +
 
 +
'''New Pure HTML + CSS styling with Labels'''
 +
 
 +
This is Richard T's updated pure HTML + CSS that uses labels to force interior elements to be part of the input.
 +
 
 +
https://jsfiddle.net/medieve/rfkvu3hm/
 +
 
 +
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 +
| Old Checkbox / Radio example (pure html+css, but less intuitive and difficult to align)
 +
|-
 +
|
 +
'''Note:''' This example/method of creating styled checkboxes or radios on a character sheet does not work out of the box, contains lots of unnecessary code, and is far less intuitive than the new button example. This example is generally not recommended to use, but is left here just for the sake of continuity, and those who might need troubleshoot their implementation.
 
[http://jsfiddle.net/waNSL/ Live Demo]
 
[http://jsfiddle.net/waNSL/ Live Demo]
  
Checkboxes and radio buttons don't like getting changed much. Instead, it can be easier to ''hide'' the actual checkbox/radio and put a more cooperative element underneath.
+
For a pure html+css approach with no sheet worker scripts, you can make the checkbox/radio invisible (but still clickable!) and overlay it on top of a more cooperative element.
<pre data-language="html" style="margin-bottom: 5px"><input type="checkbox"><span></span>
+
<pre data-language="html" style="margin-bottom: 5px">
 +
<input type="checkbox"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
 
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span></pre>
+
<input type="radio" name="attr_r"><span></span>
<pre data-language="css" style="margin-top: 5px">/* Hide actual radio/checkbox */
+
</pre>
input[type="radio"],
+
<pre data-language="css" >/* Hide actual radio/checkbox */
input[type="checkbox"] {
+
.charsheet input[type="radio"],
 +
.charsheet input[type="checkbox"] {
 
     opacity: 0;
 
     opacity: 0;
 
     width: 16px;
 
     width: 16px;
Line 67: Line 149:
  
 
/* Fake radio/checkbox */
 
/* Fake radio/checkbox */
input[type="radio"] + span::before,
+
.charsheet input[type="radio"] + span::before,
input[type="checkbox"] + span::before {
+
.charsheet input[type="checkbox"] + span::before {
 
     margin-right: 4px;
 
     margin-right: 4px;
 
     border: solid 1px #a8a8a8;
 
     border: solid 1px #a8a8a8;
Line 81: Line 163:
  
 
/* Fake radio */
 
/* Fake radio */
input[type="radio"] + span::before {
+
.charsheet input[type="radio"] + span::before {
 
     content: "";
 
     content: "";
 
     width: 12px;
 
     width: 12px;
Line 89: Line 171:
 
}
 
}
  
input[type="radio"]:checked + span::before {
+
.charsheet input[type="radio"]:checked + span::before {
 
     content: "•";
 
     content: "•";
 
}
 
}
  
 
/* Fake checkbox */
 
/* Fake checkbox */
input[type="checkbox"] + span::before {
+
.charsheet input[type="checkbox"] + span::before {
 
     content: "";
 
     content: "";
 
     width: 14px;
 
     width: 14px;
Line 102: Line 184:
 
}
 
}
  
input[type="checkbox"]:checked + span::before {
+
.charsheet input[type="checkbox"]:checked + span::before {
 
     content: "✓";
 
     content: "✓";
 
}</pre>
 
}</pre>
Line 108: Line 190:
  
 
{{note|Because of the way this is set up, '''if you do not have a span element immediately following your checkbox/radio button, the checkbox/radio button will not be visible.'''}}
 
{{note|Because of the way this is set up, '''if you do not have a span element immediately following your checkbox/radio button, the checkbox/radio button will not be visible.'''}}
 +
|}
  
=== Alternative Checkboxes ===
+
(The following examples use CSS flex for alignment. For more on flex layouts, see [[Building Character Sheets#Layout]])
[http://jsfiddle.net/su9ac/ Live Demo]
+
 
 +
=== Checkbox===
 +
<pre data-language="html" style="margin-bottom: 5px">
 +
<div class="sheet-toggle-container">
 +
  <input type="hidden" class="sheet-toggle" name="attr_show_flag" />
 +
  <button type="action" name="act_show" class="sheet-toggle">
 +
    <span class="sheet-checked">✓</span>
 +
  </button>
 +
  <span>Show?</span>
 +
</div>
 +
<script type="text/worker">
 +
  // Register the click handler to all specified buttons.
 +
  const toggleList = ["show"];
 +
  toggleList.forEach(function(button) {
 +
    on(`clicked:${button}`, function() {
 +
      const flag = `${button}_flag`;
 +
      // Check the current value of the hidden flag.
 +
      getAttrs([flag], function(v) {
 +
        // Update the value of the hidden flag to "1" for checked or "0" for unchecked.
 +
        setAttrs({
 +
          [flag]: v[flag] !== "1" ? "1" : "0"
 +
        });
 +
      });
 +
    });
 +
  });
 +
</script>
 +
</pre>
 +
<pre data-language="css" >
 +
/* Configure a container for the toggle */
 +
.charsheet .sheet-toggle-container {
 +
  display: inline-flex;
 +
  align-items: center;
 +
}
 +
 
 +
/* Configure the button styling. This example makes it look like a checkbox. */
 +
.charsheet button.sheet-toggle {
 +
  padding: 0;
 +
  border: solid 1px #a8a8a8;
 +
  cursor: pointer;
 +
 
 +
  width: 16px;
 +
  height: 16px;
 +
  border-radius: 3px;
 +
  display: flex;
 +
  justify-content: center;
 +
  align-items: center;
 +
 
 +
  font-size: 12px;
 +
}
 +
 
 +
/* Hide the "checked" section of the toggle if the attribute value is not "1". */
 +
.charsheet input.sheet-toggle:not([value="1"]) ~ button.sheet-toggle > span.sheet-checked {
 +
  display: none;
 +
}
 +
</pre>
 +
 
 +
=== Radio===
 +
<pre data-language="html" style="margin-bottom: 5px">
 +
<div class="sheet-radios">
 +
  <input type="hidden" class="sheet-radio" name="attr_level" value="1" />
 +
  <button type="action" name="act_level_1" class="sheet-radio sheet-radio-1">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_level_2" class="sheet-radio sheet-radio-2">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_level_3" class="sheet-radio sheet-radio-3">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_level_4" class="sheet-radio sheet-radio-4">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_level_5" class="sheet-radio sheet-radio-5">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
</div>
 +
<script type="text/worker">
 +
  const levelRadioValues = ["1","2","3","4","5"];
 +
  levelRadioValues.forEach(function(value) {
 +
    on(`clicked:level_${value}`, function() {
 +
      setAttrs({
 +
        ["level"]: value
 +
      });
 +
    });
 +
  });
 +
</script>
 +
</pre>
 +
<pre data-language="css" >
 +
/* Configure a container for the radio buttons. */
 +
.charsheet .sheet-radios {
 +
  display: flex;
 +
  align-items: center;
 +
}
 +
 
 +
/* Configure the button styling. This example makes it look like a radio. */
 +
.charsheet button.sheet-radio {
 +
  padding: 0;
 +
  border: solid 1px #a8a8a8;
 +
  cursor: pointer;
 +
 
 +
  width: 14px;
 +
  height: 14px;
 +
  border-radius: 50%;
 +
  display: flex;
 +
  justify-content: center;
 +
  align-items: center;
 +
}
 +
.charsheet button.sheet-radio > span.sheet-checked {
 +
  width: 6px;
 +
  height: 6px;
 +
  border-radius: 50%;
 +
  background: buttontext;
 +
}
 +
 
 +
/* Hide the "checked" section of the radio if the attribute value does not match the radio */
 +
.charsheet input.sheet-radio:not([value="1"]) ~ button.sheet-radio-1 > span.sheet-checked,
 +
.charsheet input.sheet-radio:not([value="2"]) ~ button.sheet-radio-2 > span.sheet-checked,
 +
.charsheet input.sheet-radio:not([value="3"]) ~ button.sheet-radio-3 > span.sheet-checked,
 +
.charsheet input.sheet-radio:not([value="4"]) ~ button.sheet-radio-4 > span.sheet-checked,
 +
.charsheet input.sheet-radio:not([value="5"]) ~ button.sheet-radio-5 > span.sheet-checked {
 +
  display: none;
 +
}
 +
</pre>
 +
 
 +
=== Checkbox Alternative  ===
  
 
You're not restricted to a box with a check on it if you want a binary state (on or off). When styling your checkbox (or radio button!) you can use just about anything.
 
You're not restricted to a box with a check on it if you want a binary state (on or off). When styling your checkbox (or radio button!) you can use just about anything.
 +
 +
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 +
| Old example (pure html+css)
 +
|-
 +
|
 +
[http://jsfiddle.net/su9ac/ Live Demo]
 
<pre data-language="css">/* Fake checkbox */
 
<pre data-language="css">/* Fake checkbox */
 
input[type="checkbox"] + span::before {
 
input[type="checkbox"] + span::before {
Line 129: Line 342:
 
     content: "►";
 
     content: "►";
 
}</pre>
 
}</pre>
Now, instead of an empty box, or a box with a checkmark, you've got a right-pointing arrow or a down-pointing arrow. You can also use an image instead of a string, such as <code>content: url(<nowiki>http://i.imgur.com/90HuQPr.png</nowiki>);</code>
+
You can also use an image instead of a string, such as <code>content: url(<nowiki>http://i.imgur.com/90HuQPr.png</nowiki>);</code>
 +
|}
 +
 
 +
<pre data-language="html">
 +
<input type="hidden" class="direction" name="attr_direction" value="down" />
 +
<button type="action" name="act_direction_toggle" class="direction">
 +
  <span class="down">▼</span>
 +
  <span class="right">►</span>
 +
</button>
 +
<script type="text/worker">
 +
  on("clicked:direction_toggle", function() {
 +
    // Check the current value of the hidden attribute.
 +
    getAttrs(["direction"], function(v) {
 +
      // Toggle the hidden attribute value between "down" and "right"
 +
      setAttrs({
 +
        "direction": v["direction"] !== "down" ? "down" : "right"
 +
      });
 +
    });
 +
  });
 +
</script>
 +
</pre>
 +
<pre data-language="css">
 +
/* Clear default button styling */
 +
button.sheet-direction {
 +
  margin: 0;
 +
  overflow: visible;
 +
  text-transform: none;
 +
  border-style: none;
 +
  padding: 0;
 +
  background: transparent;
 +
  cursor: pointer;
 +
}
 +
 
 +
/* Hide the section(s) that do not match the attribute value */
 +
input.sheet-direction:not([value="down"]) + button.sheet-direction > span.sheet-down {
 +
  display: none;
 +
}
 +
input.sheet-direction:not([value="right"]) + button.sheet-direction > span.sheet-right {
 +
  display: none;
 +
}
 +
</pre>
 +
 
 +
Now, instead of an empty box, or a box with a checkmark, you've got a right-pointing arrow or a down-pointing arrow. You can hide/display any other html content based on the hidden attribute value.
  
 
=== Fill Radio Buttons to the Left ===
 
=== Fill Radio Buttons to the Left ===
 +
 +
A number of games use a set of bubbles, filled in from left to right, to represent various traits. For example, selecting the third bubble in "Strength" to indicate a Strength value of 3 should also fill in bubbles 1 and 2.
 +
 +
Example by '''Dana''' - [https://jsfiddle.net/n0kyeLtm/1/ jsfiddle]
 +
<pre data-language="html">
 +
<div class="radiocontainer">
 +
  <input checked="checked" class="radiobox reset" type="radio" name="attr_strength" value="0">
 +
  <input class="radiobox" type="radio" name="attr_strength" value="1">
 +
  <input class="radiobox" type="radio" name="attr_strength" value="2">
 +
  <input class="radiobox" type="radio" name="attr_strength" value="3">
 +
  <input class="radiobox" type="radio" name="attr_strength" value="4">
 +
  <input class="radiobox" type="radio" name="attr_strength" value="5">
 +
  <input class="radiobox" type="radio" name="attr_strength" value="6">
 +
</div>
 +
</pre>
 +
<pre data-language="css">
 +
.radiocontainer {
 +
  display: flex;
 +
  margin: 2rem;
 +
  gap: 10px;
 +
}
 +
 +
.radiobox {
 +
  order: 1;
 +
  border: 1px solid black;
 +
  border-radius: 50%;
 +
  background: grey;
 +
  height: 2rem;
 +
  width: 2rem;
 +
  appearance: none;
 +
  -webkit-appearance: none;
 +
  cursor: pointer;
 +
  z-index: 1;
 +
}
 +
 +
.radiobox:checked {
 +
  opacity: 0;
 +
  z-index: 0;
 +
  position: absolute;
 +
}
 +
 +
.radiobox:checked ~ .radiobox {
 +
  order: 3;
 +
  background-color: transparent;
 +
}
 +
 +
.radiobox.reset {
 +
  order: 2;
 +
  background-color: black;
 +
}
 +
</pre>
 +
 +
'''Note:'''
 +
* When using this trick inside a [[BCS/Repeating Section|Repeating Section]], it will not work if there are underscores in an action button name. The above buttons should be named <code>attr_strength1</code> or <code>attr_strength-1</code> - they cant be <code>attr_strength_1</code>.
 +
* All radio buttons which are siblings will be affected by the selection of one of the radios. It is therefore recommended that you wrap the button group in some element, such as span or div.
 +
 +
[[File:Filled-buttons.gif|thumbnail|500px|right]]
 +
'''Alternative(older) implementations:'''
 +
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 +
| HTML/CSS+ sheetworker implementation
 +
|-
 +
|<pre data-language="html">
 +
<div class="sheet-dots">
 +
  <input type="hidden" name="attr_strength" class="sheet-dot" value="1" />
 +
  <button type="action" name="act_strength_1" class="dot">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_strength_2" class="sheet-dot sheet-gt-1">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_strength_3" class="sheet-dot sheet-gt-1 sheet-gt-2">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_strength_4" class="sheet-dot sheet-gt-1 sheet-gt-2 sheet-gt-3">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
  <button type="action" name="act_strength_5" class="sheet-dot sheet-gt-1 sheet-gt-2 sheet-gt-3 sheet-gt-4">
 +
    <span class="sheet-checked"></span>
 +
  </button>
 +
</div>
 +
<script type="text/worker">
 +
  const strengthValues = ["1","2","3","4","5"];
 +
  strengthValues.forEach(function(value) {
 +
    on(`clicked:strength_${value}`, function() {
 +
      setAttrs({
 +
        "strength": value
 +
      });
 +
    });
 +
  });
 +
</script>
 +
</pre>
 +
<pre data-language="css">
 +
.charsheet .sheet-dots{
 +
  display:flex;
 +
}
 +
/* Configure the button styling. This example makes it look like a radio. */
 +
.charsheet button.sheet-dot {
 +
  padding: 0;
 +
  border: solid 1px #a8a8a8;
 +
  cursor: pointer;
 +
  width: 14px;
 +
  height: 14px;
 +
  border-radius: 50%;
 +
  display: flex;
 +
  justify-content: center;
 +
  align-items: center;
 +
}
 +
 +
.charsheet button.sheet-dot > span {
 +
  width: 6px;
 +
  height: 6px;
 +
  border-radius: 50%;
 +
  background: buttontext;
 +
}
 +
 +
/* Hide the "checked" section of the radio if the hidden attribute value is greater than the button value */
 +
.charsheet input.sheet-dot[value="1"] ~ button.sheet-gt-1 > span.sheet-checked {
 +
  display: none;
 +
}
 +
.charsheet input.sheet-dot[value="2"] ~ button.sheet-gt-2 > span.sheet-checked {
 +
  display: none;
 +
}
 +
.charsheet input.sheet-dot[value="3"] ~ button.sheet-gt-3 > span.sheet-checked {
 +
  display: none;
 +
}
 +
.charsheet input.sheet-dot[value="4"] ~ button.sheet-gt-4 > span.sheet-checked {
 +
  display: none;
 +
}
 +
</pre>
 +
 +
Here, the <code>gt-*</code> classes are used to indicate "greater than" a particular value. This doesn't use any kind of math, so all distinct <code>gt-*</code> classes have to be included.  For example, dot 3 has classes <code>gt-1</code> ''and'' <code>gt-2</code> because 3 is greater than both 1 and 2.  Dot 1 does not have any <code>gt-*</code> classes because it is not greater than any of the other options. (If a zero option is possible, then all of these buttons will need a <code>gt-0</code> class.)
 +
 +
Each possible value of the attribute needs a corresponding CSS rule to hide all values greater than that value.  (The value of "5" doesn't need a rule here because there are no options greater than 5.)
 +
 +
This example uses a subtractive approach, meaning a that by default the button will indicate it is checked unless a CSS rule hides the "checked" span.
 +
|}
 +
 +
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 +
| Old html+css implementation
 +
|-
 +
|
 
[http://jsfiddle.net/sqaz6/ Live Demo]
 
[http://jsfiddle.net/sqaz6/ Live Demo]
  
A number of games use a set of bubbles, filled in from left to right, to represent various traits. Radio buttons can only have one selected value, however, and if we used a set of checkboxes, it would be annoying to make the user click each and every one of them to set the character's attribute. Also, a set of checkboxes would make macros extremely ugly: <code>@{Strength_1} * 1 + @{Strength_2} * 1 + ...</code>
+
Radio buttons can only have one selected value, however, and if we used a set of checkboxes, it would be annoying to make the user click each and every one of them to set the character's attribute. Also, a set of checkboxes would make macros extremely ugly: <code>@{Strength_1} * 1 + @{Strength_2} * 1 + ...</code>
  
 
However, with the radio button styling, we can solve this problem and use a radio button anyway, and only have one value.
 
However, with the radio button styling, we can solve this problem and use a radio button anyway, and only have one value.
<pre data-language="html" style="margin-bottom: 5px"><input type="radio" name="attr_r" value="1" checked="checked"><span></span>
+
<pre data-language="html">
 +
<input type="radio" name="attr_r" value="1" checked="checked"><span></span>
 
<input type="radio" name="attr_r" value="2"><span></span>
 
<input type="radio" name="attr_r" value="2"><span></span>
 
<input type="radio" name="attr_r" value="3"><span></span>
 
<input type="radio" name="attr_r" value="3"><span></span>
 
<input type="radio" name="attr_r" value="4"><span></span>
 
<input type="radio" name="attr_r" value="4"><span></span>
<input type="radio" name="attr_r" value="5"><span></span></pre>
+
<input type="radio" name="attr_r" value="5"><span></span>
<pre data-language="css" style="margin-top: 5px">/* Hide actual radio */
+
</pre>
input[type="radio"] {
+
<pre data-language="css">
 +
/* Hide actual radio */
 +
.charsheet input[type="radio"] {
 
     opacity: 0;
 
     opacity: 0;
 
     width: 16px;
 
     width: 16px;
Line 156: Line 555:
  
 
/* Fake radio */
 
/* Fake radio */
input[type="radio"] + span::before {
+
.charsheet input[type="radio"] + span::before {
 
     margin-right: 4px;
 
     margin-right: 4px;
 
     border: solid 1px #a8a8a8;
 
     border: solid 1px #a8a8a8;
Line 174: Line 573:
  
 
/* Remove dot from all radios _after_ selected one */
 
/* Remove dot from all radios _after_ selected one */
input[type="radio"]:checked ~ input[type="radio"] + span::before {     
+
.charsheet input[type="radio"]:checked ~ input[type="radio"] + span::before {     
 
     content: "";
 
     content: "";
}</pre>
+
}
 +
</pre>
 
Here, ''all'' radio buttons are styled by default to appear as though they're checked. The radio buttons ''after'' the one that's actually checked then have the dot removed. The result is that the checked radio button and all of the ones to the left are "filled in," while the ones to the left are empty. You can invert this behavior (right of the checked radio are filled, checked and left of checked are empty) by swapping the two <code>content</code> lines.
 
Here, ''all'' radio buttons are styled by default to appear as though they're checked. The radio buttons ''after'' the one that's actually checked then have the dot removed. The result is that the checked radio button and all of the ones to the left are "filled in," while the ones to the left are empty. You can invert this behavior (right of the checked radio are filled, checked and left of checked are empty) by swapping the two <code>content</code> lines.
  
 
To reverse this behavior (checked radio and right of checked radio are filled, left of checked radio are empty), swap the two <code>content</code> lines and change the last selector to this:
 
To reverse this behavior (checked radio and right of checked radio are filled, left of checked radio are empty), swap the two <code>content</code> lines and change the last selector to this:
<pre data-language="css">input[type="radio"]:checked ~ input[type="radio"] + span::before,
+
<pre data-language="css" >
input[type="radio"]:checked + span::before</pre>
+
input[type="radio"]:checked ~ input[type="radio"] + span::before,
 +
input[type="radio"]:checked + span::before
 +
</pre>
 
{{note|If no radio button is selected, all of them will appear filled in (or all will appear empty if you've reversed/inverted the CSS). Therefore, it is wise to include <code><nowiki>checked="checked"</nowiki></code> on one of the radio buttons. That said, you may desire a "zero" value for the trait in question. I recommend having an extra radio button with <code><nowiki>value="0" checked="checked"</nowiki></code> and '''without''' the span element immediately following it. This will give you an initial value of 0, your radio button group will appear as intended, and the "zero" value will not show up to the user.}}
 
{{note|If no radio button is selected, all of them will appear filled in (or all will appear empty if you've reversed/inverted the CSS). Therefore, it is wise to include <code><nowiki>checked="checked"</nowiki></code> on one of the radio buttons. That said, you may desire a "zero" value for the trait in question. I recommend having an extra radio button with <code><nowiki>value="0" checked="checked"</nowiki></code> and '''without''' the span element immediately following it. This will give you an initial value of 0, your radio button group will appear as intended, and the "zero" value will not show up to the user.}}
 
+
|}
{{note|All radio buttons which are siblings will be affected by the selection of one of the radios. It is therefore recommended that you wrap the button group in some element, such as span or div.}}
+
  
 
=== Circular Layouts ===
 
=== Circular Layouts ===
Line 190: Line 591:
  
 
Some character sheets have rather interesting layouts. [[Mage: the Ascension]], for example, has a pair of traits called Quintessence and Paradox that are both mapped onto a wheel of checkboxes.
 
Some character sheets have rather interesting layouts. [[Mage: the Ascension]], for example, has a pair of traits called Quintessence and Paradox that are both mapped onto a wheel of checkboxes.
<pre data-language="html" style="margin-bottom: 5px"><div>
+
<pre data-language="html">
 +
<div>
 
     <div>
 
     <div>
 
         <input type="checkbox" class="sheet-wheel sheet-wheel9 sheet-middle sheet-left-1" value="1"><span></span>
 
         <input type="checkbox" class="sheet-wheel sheet-wheel9 sheet-middle sheet-left-1" value="1"><span></span>
Line 216: Line 618:
 
     </div>
 
     </div>
 
     <div class="sheet-marker">•</div>
 
     <div class="sheet-marker">•</div>
</div></pre>
+
</div>
<pre data-language="css" style="margin-top: 5px">/* Hide actual checkbox */
+
</pre>
input[type="checkbox"] {
+
<pre data-language="css">
 +
/* Hide actual checkbox */
 +
.charsheet input[type="checkbox"] {
 
     position: absolute;
 
     position: absolute;
 
     opacity: 0;
 
     opacity: 0;
Line 229: Line 633:
  
 
/* Fake checkbox */
 
/* Fake checkbox */
input[type="checkbox"] + span::before {
+
.charsheet input[type="checkbox"] + span::before {
 
     border: solid 1px #a8a8a8;
 
     border: solid 1px #a8a8a8;
 
     line-height: 14px;
 
     line-height: 14px;
Line 247: Line 651:
  
 
/* Styles unique to fake checkbox (checked) */
 
/* Styles unique to fake checkbox (checked) */
input[type="checkbox"]:checked + span::before {
+
.charsheet input[type="checkbox"]:checked + span::before {
 
     content: "✓";
 
     content: "✓";
 
     color: #a00;
 
     color: #a00;
Line 254: Line 658:
  
 
/* Position checkboxes vertically in circle */
 
/* Position checkboxes vertically in circle */
input.sheet-top  { margin-top: 5px; }
+
.charsheet input.sheet-top  { overflow:auto;white-space:pre-wrap;; }
input.sheet-top + span::before { top: 0px; }
+
.charsheet input.sheet-top + span::before { top: 0px; }
input.sheet-mid-eighth { margin-top: 12px; }
+
.charsheet input.sheet-mid-eighth { margin-top: 12px; }
input.sheet-mid-eighth + span::before  { top: 7px; }
+
.charsheet input.sheet-mid-eighth + span::before  { top: 7px; }
input.sheet-mid-quarter { margin-top: 27px; }
+
.charsheet input.sheet-mid-quarter { margin-top: 27px; }
input.sheet-mid-quarter + span::before  { top: 22px; }
+
.charsheet input.sheet-mid-quarter + span::before  { top: 22px; }
input.sheet-mid-three-eighth { margin-top: 45px; }
+
.charsheet input.sheet-mid-three-eighth { margin-top: 45px; }
input.sheet-mid-three-eighth + span::before  { top: 40px; }
+
.charsheet input.sheet-mid-three-eighth + span::before  { top: 40px; }
input.sheet-middle { margin-top: 67px; }
+
.charsheet input.sheet-middle { margin-top: 67px; }
input.sheet-middle + span::before  { top: 62px; }
+
.charsheet input.sheet-middle + span::before  { top: 62px; }
input.sheet-middle-2 { margin-top: 73px; }
+
.charsheet input.sheet-middle-2 { margin-top: 73px; }
input.sheet-middle-2 + span::before  { top: 68px; }
+
.charsheet input.sheet-middle-2 + span::before  { top: 68px; }
input.sheet-mid-five-eighth { margin-top: 95px; }
+
.charsheet input.sheet-mid-five-eighth { margin-top: 95px; }
input.sheet-mid-five-eighth + span::before  { top: 90px; }
+
.charsheet input.sheet-mid-five-eighth + span::before  { top: 90px; }
input.sheet-mid-three-quarter { margin-top: 113px; }
+
.charsheet input.sheet-mid-three-quarter { margin-top: 113px; }
input.sheet-mid-three-quarter + span::before  { top: 108px; }
+
.charsheet input.sheet-mid-three-quarter + span::before  { top: 108px; }
input.sheet-mid-seven-eighth { margin-top: 127px; }
+
.charsheet input.sheet-mid-seven-eighth { margin-top: 127px; }
input.sheet-mid-seven-eighth + span::before  { top: 122px; }
+
.charsheet input.sheet-mid-seven-eighth + span::before  { top: 122px; }
input.sheet-bottom { margin-top: 135px; }
+
.charsheet input.sheet-bottom { margin-top: 135px; }
input.sheet-bottom + span,
+
.charsheet input.sheet-bottom + span,
input.sheet-bottom + span::before  { top: 130px; }
+
.charsheet input.sheet-bottom + span::before  { top: 130px; }
  
 
/* Position checkboxes horizontally in circle */
 
/* Position checkboxes horizontally in circle */
input.sheet-left-1 { margin-left: 14px; }
+
.charsheet input.sheet-left-1 { margin-left: 14px; }
input.sheet-left-1 + span::before  { left: 14px; }
+
.charsheet input.sheet-left-1 + span::before  { left: 14px; }
input.sheet-left-2 { margin-left: 1px; }
+
.charsheet input.sheet-left-2 { margin-left: 1px; }
input.sheet-left-2 + span::before  { left: 1px; }
+
.charsheet input.sheet-left-2 + span::before  { left: 1px; }
input.sheet-left-3 { margin-left: -4px; }
+
.charsheet input.sheet-left-3 { margin-left: -4px; }
input.sheet-left-3 + span::before  { left: -4px; }
+
.charsheet input.sheet-left-3 + span::before  { left: -4px; }
input.sheet-left-4 { margin-left: -5px; }
+
.charsheet input.sheet-left-4 { margin-left: -5px; }
input.sheet-left-4 + span::before  { left: -5px; }
+
.charsheet input.sheet-left-4 + span::before  { left: -5px; }
input.sheet-left-5 { margin-left: -2px; }
+
.charsheet input.sheet-left-5 { margin-left: -2px; }
input.sheet-left-5 + span::before  { left: -2px; }
+
.charsheet input.sheet-left-5 + span::before  { left: -2px; }
input.sheet-left-6 { margin-left: 1px; }
+
.charsheet input.sheet-left-6 { margin-left: 1px; }
input.sheet-left-6 + span::before  { left: 1px; }
+
.charsheet input.sheet-left-6 + span::before  { left: 1px; }
input.sheet-left-7 { margin-left: 3px; }
+
.charsheet input.sheet-left-7 { margin-left: 3px; }
input.sheet-left-7 + span::before  { left: 3px; }
+
.charsheet input.sheet-left-7 + span::before  { left: 3px; }
input.sheet-left-8 { margin-left: 2px; }
+
.charsheet input.sheet-left-8 { margin-left: 2px; }
input.sheet-left-8 + span::before  { left: 2px; }
+
.charsheet input.sheet-left-8 + span::before  { left: 2px; }
input.sheet-left-9 { margin-left: -4px; }
+
.charsheet input.sheet-left-9 { margin-left: -4px; }
input.sheet-left-9 + span::before  { left: -4px; }
+
.charsheet input.sheet-left-9 + span::before  { left: -4px; }
input.sheet-left-10 { margin-left: -16px; }
+
.charsheet input.sheet-left-10 { margin-left: -16px; }
input.sheet-left-10 + span::before  { left: -16px; }
+
.charsheet input.sheet-left-10 + span::before  { left: -16px; }
  
 
/* Rotate checkboxes */
 
/* Rotate checkboxes */
input.sheet-wheel9,
+
.charsheet input.sheet-wheel9,
input.sheet-wheel9 + span::before { transform: rotate(9deg); }
+
.charsheet input.sheet-wheel9 + span::before { transform: rotate(9deg); }
input.sheet-wheel27,
+
.charsheet input.sheet-wheel27,
input.sheet-wheel27 + span::before { transform: rotate(27deg); }
+
.charsheet input.sheet-wheel27 + span::before { transform: rotate(27deg); }
input.sheet-wheel45,
+
.charsheet input.sheet-wheel45,
input.sheet-wheel45 + span::before { transform: rotate(45deg); }
+
.charsheet input.sheet-wheel45 + span::before { transform: rotate(45deg); }
input.sheet-wheel63,
+
.charsheet input.sheet-wheel63,
input.sheet-wheel63 + span::before { transform: rotate(63deg); }
+
.charsheet input.sheet-wheel63 + span::before { transform: rotate(63deg); }
input.sheet-wheel81,
+
.charsheet input.sheet-wheel81,
input.sheet-wheel81 + span::before { transform: rotate(81deg); }
+
.charsheet input.sheet-wheel81 + span::before { transform: rotate(81deg); }
input.sheet-wheel99,
+
.charsheet input.sheet-wheel99,
input.sheet-wheel99 + span::before { transform: rotate(99deg); }
+
.charsheet input.sheet-wheel99 + span::before { transform: rotate(99deg); }
input.sheet-wheel117,
+
.charsheet input.sheet-wheel117,
input.sheet-wheel117 + span::before { transform: rotate(117deg); }
+
.charsheet input.sheet-wheel117 + span::before { transform: rotate(117deg); }
input.sheet-wheel135,
+
.charsheet input.sheet-wheel135,
input.sheet-wheel135 + span::before { transform: rotate(135deg); }
+
.charsheet input.sheet-wheel135 + span::before { transform: rotate(135deg); }
input.sheet-wheel153,
+
.charsheet input.sheet-wheel153,
input.sheet-wheel153 + span::before { transform: rotate(153deg); }
+
.charsheet input.sheet-wheel153 + span::before { transform: rotate(153deg); }
input.sheet-wheel171,
+
.charsheet input.sheet-wheel171,
input.sheet-wheel171 + span::before { transform: rotate(171deg); }
+
.charsheet input.sheet-wheel171 + span::before { transform: rotate(171deg); }
  
div.sheet-marker {
+
.charsheet div.sheet-marker {
 
     margin: 36px 0px 0px 5px;
 
     margin: 36px 0px 0px 5px;
 
     font-size: 20px;
 
     font-size: 20px;
}</pre>
+
}
 +
</pre>
  
== Styling Select Dropdowns ==
+
== Select Dropdown==
 
[http://jsfiddle.net/ojvq39oo/ Live Demo]
 
[http://jsfiddle.net/ojvq39oo/ Live Demo]
  
 
<code><select></code> elements are notoriously difficult to apply most styles to. However, using <code>:hover</code> pseudo-selectors and radio buttons, you can create something approximating a dropdown with whatever style you like.
 
<code><select></code> elements are notoriously difficult to apply most styles to. However, using <code>:hover</code> pseudo-selectors and radio buttons, you can create something approximating a dropdown with whatever style you like.
<pre data-language="html" style="margin-bottom: 5px"><div class="sheet-container">
+
<pre data-language="html">
 +
<div class="sheet-container">
 
     <div class="sheet-child">
 
     <div class="sheet-child">
 
         <input type="radio" name="attr_radio" class="sheet-select-radio sheet-d4" value="1" checked="true" />
 
         <input type="radio" name="attr_radio" class="sheet-select-radio sheet-d4" value="1" checked="true" />
Line 338: Line 744:
 
         <div class="sheet-d8"></div>
 
         <div class="sheet-d8"></div>
 
     </div>
 
     </div>
</div></pre>
+
</div>
<pre data-language="css" style="margin-top: 5px">.sheet-container {
+
</pre>
 +
<pre data-language="css">
 +
.charsheet .sheet-container {
 
     width: 280px;
 
     width: 280px;
 
}
 
}
  
.sheet-container,
+
.charsheet .sheet-container,
.sheet-child {
+
.charsheet .sheet-child {
 
     display: inline-block;
 
     display: inline-block;
 
}
 
}
  
.sheet-child {
+
.charsheet .sheet-child {
 
     vertical-align: middle;
 
     vertical-align: middle;
 
     width: 35px;
 
     width: 35px;
Line 354: Line 762:
 
}
 
}
  
.sheet-child input,
+
.charsheet .sheet-child input,
.sheet-child input + label {
+
.charsheet .sheet-child input + label {
 
     display: none;
 
     display: none;
 
     z-index: 1;
 
     z-index: 1;
 
}
 
}
  
.sheet-child:hover {
+
.charsheet .sheet-child:hover {
 
     background: gray;
 
     background: gray;
 
     position:absolute;
 
     position:absolute;
Line 369: Line 777:
 
}
 
}
  
.sheet-child:hover > div.sheet-d4 {
+
.charsheet .sheet-child:hover > div.sheet-d4 {
 
     display: none;
 
     display: none;
 
}
 
}
  
.sheet-child:hover input,
+
.charsheet .sheet-child:hover input,
.sheet-child:hover input + label {
+
.charsheet .sheet-child:hover input + label {
 
     display: inline;
 
     display: inline;
 
}
 
}
  
.sheet-child:hover input + label {
+
.charsheet .sheet-child:hover input + label {
 
   margin-right: 50%
 
   margin-right: 50%
 
}
 
}
  
.sheet-child:hover label {
+
.charsheet .sheet-child:hover label {
 
     display: inline-block;
 
     display: inline-block;
 
}
 
}
  
div.sheet-d4 {
+
.charsheet div.sheet-d4 {
 
     background-position: -411px -1px;
 
     background-position: -411px -1px;
 
     width: 35px;
 
     width: 35px;
Line 396: Line 804:
 
}
 
}
  
div.sheet-d8 {
+
.charsheet div.sheet-d8 {
 
     background-position: -703px -1px;
 
     background-position: -703px -1px;
 
     width: 35px;
 
     width: 35px;
Line 406: Line 814:
 
}
 
}
  
.sheet-child:not(:hover) input.sheet-select-radio.sheet-d4:checked ~ div.sheet-d4,
+
.charsheet .sheet-child:not(:hover) input.sheet-select-radio.sheet-d4:checked ~ div.sheet-d4,
.sheet-child:not(:hover) input.sheet-select-radio.sheet-d8:checked ~ div.sheet-d8 {
+
.charsheet .sheet-child:not(:hover) input.sheet-select-radio.sheet-d8:checked ~ div.sheet-d8 {
 
     display: block;
 
     display: block;
 
}</pre>
 
}</pre>
  
== Styling Select Dropdowns Text ==
+
=== Select Dropdown Text===
 
Here's another method to edit text within a select. Use an '''input''' with type="hidden" given the same attribute name as the '''select''' to trigger css styles on the select's option text.
 
Here's another method to edit text within a select. Use an '''input''' with type="hidden" given the same attribute name as the '''select''' to trigger css styles on the select's option text.
<pre data-language="html" style="margin-bottom: 5px">
+
<pre data-language="html">
 
<input name="attr_favcolor" type="hidden" class="sheet-color-switch">
 
<input name="attr_favcolor" type="hidden" class="sheet-color-switch">
 
<select name="attr_favcolor" class="sheet-color-select1">
 
<select name="attr_favcolor" class="sheet-color-select1">
Line 422: Line 830:
 
</pre>
 
</pre>
 
<pre data-language="css">
 
<pre data-language="css">
.sheet-color-switch[value="0"] + .sheet-color-select1 {color: red;}
+
.charsheet .sheet-color-switch[value="0"] + .sheet-color-select1 {color: red;}
.sheet-color-switch[value="1"] + .sheet-color-select1 {color: yellow;}
+
.charsheet .sheet-color-switch[value="1"] + .sheet-color-select1 {color: yellow;}
.sheet-color-switch[value="2"] + .sheet-color-select1 {color: blue;}
+
.charsheet .sheet-color-switch[value="2"] + .sheet-color-select1 {color: blue;}
 
</pre>
 
</pre>
  
== Custom Progress Bar ==
+
=== Optgroup ===
To create a custom progress bar, i use css and sheet worker to update an hidden field:
+
[[File:Char-sheet-option-element-styled.png|thumbnail|right|400px| How it looks on chrome. Firefox ignores al/most of styling.]]
 +
If optgroup doesn't work, one workaround is to add a styled, [https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled disabled] <code><nowiki><option></nowiki></code> in place of <code><nowiki><optgroup></nowiki></code>-element to work in a similar way.
 +
 
 +
select/option can be hard to style, and sometimes browsers don't show the styling. In the example, <code><nowiki>&amp;nbsp;</nowiki></code> are used to indent the names, as padding & margin doesn't seem to work.
  
[https://jsfiddle.net/32gt5cvc/ Live Demo]
+
<pre data-language="html">
<pre data-language="html" style="margin-bottom: 5px"><div class="sheet-container">
+
<select name="attr_ancestry">
<input type="hidden" value="2" name="attr_Character_overweight" class="sheet-hidden sheet-overweight">
+
  <option disabled style="background:gray;font-weight:bold;color:white;">&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MEN</option>
<div class="sheet-overweight"></div>
+
  <option value="Imperal">Imperal</option>
</div>
+
  <option value="Breton">Breton</option>
 +
  <option value="Redguard">Redguard</option>
 +
  <option value="Nord">Nord</option>
 +
  <option disabled style="background:gray;font-weight:bold;color:white;">&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MER</option>
 +
  <option value="Altmer">Altmer</option>
 +
  <option value="Bosmer">Bosmer</option>
 +
  <option value="Dunmer">Dunmer</option>
 +
  <option value="Orsmer">Orsmer</option>
 +
</select>
 
</pre>
 
</pre>
  
<pre data-language="css" style="margin-top: 5px">div.sheet-overweight {
+
== Button==
    width: 90%;
+
See the '''[[Button#Styling_Roll_Buttons|Styling Roll Buttons]]''' on the [[Button]]-page for more examples. The examples can be adjusted to work with [[Button#Action_Button|Action]] and Compendium buttons.
    height: 20px;
+
 
    border: 1px solid black;
+
How to remove the default d20 from roll buttons:
    color: black;
+
 
    text-align: center;
+
'''CSS:'''
 +
<pre data-language="css">
 +
.charsheet button[type=roll].sheet-blank-button::before {
 +
  content: '';
 
}
 
}
input.sheet-overweight[value="0"] ~ div.sheet-overweight {
+
</pre>
    background: white;
+
 
}
+
'''HTML:'''
input.sheet-overweight[value="1"] ~ div.sheet-overweight {
+
<pre data-language="html">
    background: linear-gradient(to left, white 60%, green 75%);   
+
<button class="sheet-blank-button" name="roll_BluffCheck" value="/roll 1d20 + @{Bluff}" type="roll">Bluff</button>
}
+
</pre>
input.sheet-overweight[value="2"] ~ div.sheet-overweight {
+
 
    background: linear-gradient(to left, white 40%, yellow 75%);
+
The d20 is a single character with the [[CSS_Wizardry#dicefontd20|dicefontd20]] font-family in the button's <code>::before</code> pseudo-element. Setting the content to an empty string removes it.
}
+
 
    input.sheet-overweight[value="2"] ~ div.sheet-overweight:before {
+
== Repeating Sections ==
        content:"Bags half full";
+
''main article:'' '''[[BCS/Repeating_Sections#Styling Repeating Sections|Repeating Sections - Styling]]'''
    }
+
 
input.sheet-overweight[value="3"] ~ div.sheet-overweight {
+
It's possible to style your [[Repeating Sections]] in a variety of ways, but it's a bit trickier than elsewhere on the sheet. You can't just write your CSS as though the <code><fieldset></code> that's in your HTML source is what the user is viewing, as roll20 creates their own elements and css for things that are inside.
    background: linear-gradient(to left, white 20%, orange 75%);
+
 
}
+
You can do numerous things to alter how your repeating sections are displayed on the final character sheet. For example, you can have multiple repeating items per row:
    input.sheet-overweight[value="3"] ~ div.sheet-overweight:before {
+
<pre data-language="css" >
        content: "Bags nearly full";
+
.charsheet .repcontainer[data-groupname="repeating_skills"] > .repitem {
    }
+
     display: inline-block;
input.sheet-overweight[value="4"] ~ div.sheet-overweight {
+
    background: linear-gradient(to left, white, red 100%);
+
}
+
    input.sheet-overweight[value="4"] ~ div.sheet-overweight:before {
+
        content: "Bags full !";
+
    }
+
input.sheet-overweight[value="5"] ~ div.sheet-overweight {
+
     background: black;
+
    color: white;
+
}
+
    input.sheet-overweight[value="5"] ~ div.sheet-overweight:before {
+
        content: "Bags too heavy !";
+
 
}</pre>
 
}</pre>
 +
{{note|You do '''not''' prefix the rep* classes with <code>sheet-</code>!}}
 +
 +
Remember to use the <code>[data-groupname="repeating_..."]</code> attribute selector, if you want to only apply the style to a single repeating section. Of course, if you want the style to affect all of your repeating sections, that's not needed.
 +
 +
'''What Can't You Do?'''
 +
 +
You cannot:
 +
* Change the <code>display</code> property of the original <code><fieldset></code>.
 +
* Change the text of the Add, Modify/Done, Delete, or Move buttons.
 +
** However, you could set their opacity to 0 and display something in their place, much like [[CSS Wizardry#Styling Checkboxes and Radio Buttons|styling checkboxes and radios]], as well as add <code>::before</code> or <code>::after</code> pseudo-elements to them.
 +
* Change the <code>display</code> property of the '''Add''' button after the user has pressed '''Modify''' once.
 +
 +
 +
'''How they are rendered on the sheet'''
  
== Styling Repeating Sections ==
+
After writing the code for your [[BCS/Repeating Sections|repeating section]] on your html file, here is how it will look when rendered to the user:
It's possible to style your repeating sections in a variety of ways. However, you can't just write your CSS as though the <code><fieldset></code> that's in your HTML source is what the user is viewing. After writing the code for your repeating section, here is how it will look when rendered to the user:
+
<pre data-language="html" >
<pre data-language="html"><fieldset class="repeating_my-repeating-section" style="display: none;">
+
<fieldset class="repeating_my-repeating-section" style="display: none;">
     <!-- my-repeating-section HTML -->
+
     <!-- how the repating section code is structured in the browser after roll20 have created the parts inside it -->
 
</fieldset>
 
</fieldset>
 
<div class="repcontainer" data-groupname="repeating_my-repeating-section">
 
<div class="repcontainer" data-groupname="repeating_my-repeating-section">
Line 495: Line 919:
 
     <button class="btn repcontrol_add">+Add</button>
 
     <button class="btn repcontrol_add">+Add</button>
 
</div></pre>
 
</div></pre>
When you click the Modify button, the Add button is is set to <code>display: none</code> and the text of the Modify button is changed to "Done". When you click Done, the Add button is set to <code>display: inline-block</code> and the text of the Done button is changed to "Modify". While modifying repitems, the repcontainer gains the class "editmode".
 
  
Armed with this knowledge, you can do numerous things to alter how your repeating sections are displayed on the final character sheet. For example, you can have multiple repeating items per row:
+
When you click the '''Modify button''', the '''Add''' button is is set to <code>display: none;</code> and the text of the Modify button is changed to "Done". When you click Done, the Add button is set to <code>display: inline-block;</code> and the text of the Done button is changed to "Modify". While modifying repitems, the repcontainer gains the class "editmode".
<pre data-language="css">.repcontainer[data-groupname="repeating_example"] > .repitem {
+
    display: inline-block;
+
}</pre>
+
{{note|You do '''not''' prefix the rep* classes with <code>sheet-</code>!}}
+
  
Remember to use the <code>[data-groupname="repeating_..."]</code> attribute selector if you want to only apply the style to a single repeating section. Of course, if you want the style to affect all of your repeating sections, that's not needed.
+
==Roll Templates==
 +
Apart from styling your own rolltemplates, you can also adjust the style of the [[Default Roll Template]].
  
=== What Can't You Do? ===
+
See '''[[Building_Character_Sheets/Roll_Templates#Styling_Roll_Templates|Styling Roll Templates]]'''
You cannot:
+
* Change the "display" property of the original <code><fieldset></code>.
+
* Change the text of the Add, Modify/Done, Delete, or Move buttons.
+
** However, you could set their opacity to 0 and display something in their place, much like [[#Styling Checkboxes and Radio Buttons|styling checkboxes and radios]], as well as add <code>::before</code> or <code>::after</code> pseudo-elements to them.
+
* Change the "display" property of the Add button after the user has pressed Modify once.
+
  
=== Counting Items ===
+
== Fonts ==
[https://jsfiddle.net/7cy6uf2j/ Live Demo]
+
Roll20 supports five fonts ('''Arial''', '''Patrick Hand''', '''Contrail One''', '''Shadows Into Light''', and '''Candal''') by default.
  
CSS can let you count things. This can be applied in multiple contexts for character sheets, but one option is to count how many repeating items are in your repeating section:
+
All the [https://www.w3schools.com/cssref/css_websafe_fonts.asp Websafe Fonts] should also be available.
<pre data-language="html" style="margin-bottom:5px">
+
<div class="sheet-repeating-fields">
+
    <fieldset class="repeating_my-repeating-section">
+
        <span class="sheet-counter"></span>
+
        <input type="text" name="attr_example">
+
    </fieldset>
+
</div>
+
</pre>
+
  
<pre data-language="css" style="margin-top:5px">.sheet-repeating-fields {
+
<pre data-language="css">
  counter-reset: sheet-rep-items;
+
.charsheet span{
 +
// defining more than one fint will only use the first one, but will fall back to the next font if the first one doesn't work for some reason.
 +
  font-family: "Arial", "Patrick Hand";
 
}
 
}
 +
</pre>
  
.sheet-counter::before {
+
See Also '''[[#Icon Fonts]]''' for the built-in special font on Roll20.
  counter-increment: sheet-rep-items;
+
  content: counter(sheet-rep-items) '. ';
+
}</pre>
+
  
=== Filter Repeating section displays according to criteria within the repeating items itself ===
+
===Google Fonts===
 +
{{notebox| When creating or editing Legacy Sanitized sheets, follow the guide '''exactly''' to make it work, you can't use urls generated by google. All fonts must be loaded in a single <code>@import</code>.}}
 +
{{orange| '''Bug:''' Seems impossible to use any font that includes <code>eval</code> in the name.{{forum|permalink/9886582/ discussion}} [[BCS/Bugs]] [[User:1223200|1223200]] ([[User talk:1223200|talk]]) 10 March 2021 (UTC) }}
  
Assume you want a tabbed section to organize/display spells. All the 1st circle spells are displayed under tab 1, all the 2nd circle spells under tab 2, Etc. One way to do this is to duplicate the html code for each spell level. You have 10 lists, and the tabs choose which of the 10 lists to display. The following code shows how to have one list, but to filter the list so it only displays certain spells.
+
Roll20 Character sheets supports importing fonts from [https://fonts.google.com/ Google Fonts].
  
The HTML looks something like this.
+
* '''[https://docs.google.com/document/d/1nZiUHkWcQNeoypMzVpAWPC63TafmRCQXy6NxdQ7S8mY/edit Great Fonts for Character Sheets]''' - Google Docs displaying a wide range of fonts; from medieval, futuristic, old western, to gothic & horror
<pre data-language="html" style="margin-bottom:5px">
+
    <span class="sheet-fixed-width" style="width: 11em;"><b> Spell List </b></span><br>
+
    <input type="radio" name="attr_tab-spells" class="sheet-tab sheet-spells-tab0" value="0" title="All circles" checked style="margin-left:1px;"/>
+
    <span class="sheet-tab sheet-spells-tab0">All</span>
+
    <input type="radio" name="attr_tab-spells" class="sheet-tab sheet-spells-tab1" value="1" title="Circle 1"/>
+
    <span class="sheet-tab sheet-spells-tab1">Circle 1</span>
+
    <input type="radio" name="attr_tab-spells" class="sheet-tab sheet-spells-tab2" value="2" title="Circle 2"/>
+
    <span class="sheet-tab sheet-spells-tab2">Circle 2</span>
+
    <input type="radio" name="attr_tab-spells" class="sheet-tab sheet-spells-tab3" value="3" title="Circle 3"/>
+
    <span class="sheet-tab sheet-spells-tab3">Circle 3</span>
+
  
    <div class="sheet-section sheet-section-sp-list">
+
For now, this is limited to Google Fonts: [https://fonts.googleapis.com fonts.googleapis.com]
        <fieldset class="repeating_spell">
+
            <input type="radio" name="attr_SP_Circle" class="sheet-hidden sheet-spells-tab0" value="0" checked />
+
            <input type="radio" name="attr_SP_Circle" class="sheet-hidden sheet-spells-tab1" value="1"/>
+
            <input type="radio" name="attr_SP_Circle" class="sheet-hidden sheet-spells-tab2" value="2"/>
+
            <input type="radio" name="attr_SP_Circle" class="sheet-hidden sheet-spells-tab3" value="3"/>
+
            <div class="sheet-filtered-box">
+
                <span><select name="attr_SP_Circle" class="sheet-nowrap sheet-label-down" style="width:5em;">
+
                    <option value="0" selected title"Please choose the circle for this spell.">-</option>
+
                    <option value="1">1</option>
+
                    <option value="2">2</option>
+
                    <option value="3">3</option>
+
                </select>Circle</span>
+
                <span class="sheet-nowrap">
+
                        <input name="attr_SP_Name" type="text" style="width:12em;" placeholder="Spell Name" ></span>
+
  
More fields.
+
[http://www.mortaine.com/blog/2020/02/21/using-google-fonts-in-roll20-character-sheets/ Video Guide to use Google Fonts] by '''Stephanie'''
            </div>
+
 
        </fieldset>
+
 
    </div>
+
'''New/Normal Sheets'''
 +
 
 +
When editing (default)[[Character Sheet Enhancement|CSE]]-sheets, you can use the <code>@import</code> styles generated by google as-is, including weights and variations; at least for the sheet's styling itself. However, since the roll templates still use the old style parser, you will need to use the legacy form below in order to use the font in your roll template.
 +
 
 +
'''Mobile'''
 +
 
 +
Roll20's [[mobile]] app currently has its own quirks for font use: [[BCS/Mobile#Custom_Fonts]].
 +
 
 +
 
 +
====[[Legacy Sheet]]====
 +
The following instruction applies if you are working with a [[Legacy Sheet]].
 +
 
 +
[[Legacy Sheets]] strictly uses the CSS-1 rule for importing fonts which is different from the auto-generated CSS-2 <code>@import</code> rule, which Google now uses ''(as of April 2020)''.
 +
 
 +
'''Examples of a Roll20 formatted import rules''':
 +
 
 +
For the font "Sigmar One":
 +
<pre data-language="css">
 +
@import url('https://fonts.googleapis.com/css?family=Sigmar+One&display=swap');
 
</pre>
 
</pre>
The relevant part of the css looks like this.
 
<pre data-language="html" style="margin-bottom:5px">
 
div.sheet-section-sp-list {
 
max-height: 999999px;
 
visibility: visible;
 
opacity: 1;
 
transition: opacity 0.5s linear 0s;
 
overflow: hidden;
 
}
 
  
.charsheet .repcontainer .repitem,         /* A filterbox should look exactly like a repitem.  */
+
or multiple font-families into Roll20, importing "Zilla Slab" and "Anton":
.sheet-filtered-box {
+
    padding: 5px 5px 2px 5px;
+
    margin: 5px;
+
border: 2px solid #CCCCCC;
+
border-radius: 3px;
+
}
+
  
.repcontainer .repitem {
+
<pre data-language="css">
    overflow: hidden;
+
@import url('https://fonts.googleapis.com/css?family=Zilla+Slab|Anton&display=swap');
}
+
</pre>
 +
 
 +
'''Example of an autogenerated @import rule by Google Fonts'''
 +
for "Anton" and "Roboto" ''({{red|that doesn't work out-of-the-box}})'':
 +
<pre data-language="css">
 +
@import url('https://fonts.googleapis.com/css2?family=Anton&family=Roboto&display=swap');
 +
</pre>
 +
 
 +
Things that requires changes:
 +
 
 +
1. Edit the <code>css2</code> to <code>css</code> when importing fonts.
 +
 
 +
2. When bringing in multiple fonts, you will need to replace <code>&</code> with <code>|</code>, and you do not need to repeat the <code>family=</code>
 +
 
 +
{{notebox|Roll20(or just [[LCS]]?) doesn't support [https://fonts.google.com/knowledge/topics/variable_fonts variable fonts], which is part of the CSS2 upgrade. It will bring in the font family {{code|Arvo}}, but will not bring in {{code|Arvo:ital@1}}.}}
 +
 
 +
3. Some fonts may require a font weight. ie <code>Open+Sans+Condensed:wght@300</code> For some reason this may not load in Roll20.  Instead, try omitting <code>wght@</code> from the snippet. example: <code>Open+Sans+Condensed:300</code>
 +
 
 +
''A quick template for use to construct your own rule.''
 +
 
 +
<pre data-language="css">
 +
@import url('https://fonts.googleapis.com/css?family=NAME+OF+FONT+YOU+WANT&display=swap');
 +
</pre>
 +
 
 +
You can then call the font from inside your CSS with the <code>font-family</code>-attribute:
 +
 
 +
<pre data-language="css">
 +
// imports the "Sigmar One" font to the sheet & rolltemplates
 +
@import url('https://fonts.googleapis.com/css?family=Sigmar+One&display=swap');
  
.sheet-section-sp-list>fieldset.repeating_spell+.repcontainer>.repitem {    /* A repitem within a spellslist should functionally not exist, it's place is taken by a filterbox. */
+
.charsheet span{
     padding: 0px;
+
     // sets all span to use "Sigmar One", and "Arial" is made a fallback font, if Sigmar doesn't work
    margin: 0px;
+
     font-family: 'Sigmar One', Arial;
    border: none;
+
border-radius:;
+
 
}
 
}
 +
</pre>
  
.sheet-filtered-box {       /* By default, display no items of a filter box */
+
====Material Icons====
    display: none;
+
You can use Google's [https://developers.google.com/fonts/docs/material_icons Material Icons]-font, but it works slightly differently than other google fonts. You import the font together with rest of the google fonts, but instead of defining a font-family for when you want to use it, instead you just add {{c|material-icons}} as a class, and then write the icons name in the element, or use the special character for it.
 +
 
 +
[https://fonts.google.com/icons List of material Icons & their names]
 +
 
 +
Minimal working example:
 +
 
 +
<pre data-language="html">
 +
<span class="material-icons">cloud</span>
 +
<span class="material-icons blue">face</span>
 +
<span class="material-icons">&#xE87C;</span>
 +
</pre>
 +
 
 +
<pre data-language="css">
 +
@import url('https://fonts.googleapis.com/css?family=Material+Icons&display=swap');
 +
 
 +
.charsheet .blue{
 +
  color: blue;
 
}
 
}
            /* Decide which specific repitems to display.  */
 
input.sheet-spells-tab0:checked~.sheet-filtered-box,      /* Always display all spells of circle zero.  */
 
.sheet-spells-tab0:checked~.sheet-section-sp-list>fieldset.repeating_spell+.repcontainer>.repitem .sheet-filtered-box,  /* When tab0 is checked, display everything no matter what which of the inner boxes are checked. */
 
.sheet-spells-tab1:checked~.sheet-section-sp-list>fieldset.repeating_spell+.repcontainer>.repitem input.sheet-spells-tab1:checked~.sheet-filtered-box,
 
.sheet-spells-tab2:checked~.sheet-section-sp-list>fieldset.repeating_spell+.repcontainer>.repitem input.sheet-spells-tab2:checked~.sheet-filtered-box,
 
.sheet-spells-tab3:checked~.sheet-section-sp-list>fieldset.repeating_spell+.repcontainer>.repitem input.sheet-spells-tab3:checked~.sheet-filtered-box {
 
display: block;
 
}
 
 
</pre>
 
</pre>
  
So basically what this does is it makes a filterbox look exactly like a repitem.
+
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
By default, no filterboxes are displayed.
+
| '''Useful icons for Roll20 Sheets'''
The last css entries basically say where tab X above the repeating field is checked, and the same radio button inside a specific repitem is checked, then display that item.
+
|-
 +
|* Tracking: {{c|star-outline}}, {{c|star-half}}, {{c|star}}, {{c|favorite}}, {{c|favorite_border}}, {{c|key}}, {{c|money}}, {{c|ticket-star}},  {{c|power}}, {{c|power_off}}, {{c|diamond}}
 +
** '''Toggle''' {{c|toggle_on}}, {{c|toggle_off}}, {{c|check_box}}, {{c|check_box_outline_blank}}
 +
** {{c|battery}}, {{c|battery-flash}}, {{c|battery-unknown}}, {{c|battery-alert}}, {{c|battery_0_bar}} - {{c|battery_6_bar}}, {{c|battery_full}}
 +
** {{c|thumb-up}}, {{c|thumb-down}}, {{c|thumb-up-down}}
 +
* Status Effects & Similar: {{c|hospital}}, {{c|healing}}, {{c|fire}}, {{c|severe_cold}}, {{c|flare}}, {{c|flash}}, {{c|fire}}, {{c|bolt}}, {{c|waves}}, {{c|air}}, {{c|shield-security}},  {{c|flower}}, {{c|cloud-outline}}, {{c|flower-alt}}, {{c|sun}}, {{c|walk}}, {{c|run}}, {{c|mood}}, {{c|mood-sad}}, {{c|accounts}}(great for duplicate/mirror image), {{c|volume-off}}, {{c|hearing}}, {{c|hearing-disabled}} {{c|hotel}}(rest symbol}}, {{c|mic-off}}(silenced effect?), {{c|balance}}, {{c|sick}}, {{c|speed}}, {{c|thermostat}}, {{c|auto_fix_high}}(magic wand), {{c|emergency}}, {{c|heart_broken}}, {{c|water_drop}}
 +
* Travel/Downtime: {{c|cutlery}}, {{c|compass}}, {{c|landscape}}, {{c|park}}, {{c|nature}}, {{c|map}}, {{c|hiking}}, {{c|hearing}}, {{c|liqour}}, {{c|home}}, {{c|castle}}, {{c|church}}, {{c|forest}}, {{c|grass}}, {{c|fort}}, {{c|local_police}}, {{c|mosque}}, {{c|sailing}}, {{c|temple_buddhist}}, {{c|temple_hindu}}, {{c|warehouse}}
 +
* Settings, config, menu: {{c|flag}}, {{c|menu}}, {{c|more}}, {{c|more-vert}}, {{c|settings}}, {{c|wrench}}, {{c|help}}, {{c|alert-circle}}, {{c|alert-triangle}}, {{c|info}}, {{c|help}}, {{c|close-circle-o}}, {{c|close-circle}}, {{c|close}}, {{c|push_pin}}
 +
** {{c|functions}}(edit macros?)
 +
** colors, visibility: {{c|palette}}, {{c|eye}}, {{c|eye-off}}, {{c|format-color-fill}}, {{c|invert-colors}}, {{c|invert-colors-off}}
 +
** light/dark: {{c|light_mode}}, {{c|mode_night}}, {{c|lamp}}, {{c|brightness-2}}(moon), {{c|brightness-5}}(sun)
 +
** {{c|upload}}, {{c|download}}, {{c|print}}, {{c|smartphone}}
 +
* Misc Modern: {{c|portable-wifi-changes}}(looks like a radar), {{c|bug}}, {{c|radio}}, {{c|router}}, {{c|sacnner}} {{c|input-antenna}}, {{c|wifi-alt}}, {{c|car}}, {{c|city}},  {{c|cocktail}}, {{c|boat}}, {{c|cafe}}, {{c|gas-satation}}, {{c|map}}, {{c|traffic}},  {{c|devices}}, {{c|globe}}, {{c|rocket}}, {{c|satellite_alt}},  {{c|search}}, {{c|cell_tower}}, {{c|nightlife}}, {{c|science}}, {{c|psychology}}, {{c|vaccines}}, {{c|coronavirus}}
 +
* Contribute/Sheet Dev: {{c|github}}, {{c|translate}}
 +
|}
  
=== Duplicate a repeating section name to display the same data on more than one tab.or present a summary of the data elsewhere ===
+
===Custom Fonts===
 +
It's still possible to use custom fonts, but it's not guaranteed to work and requires the user who want to see them to disable browser  brower security to allow "unsecure scripts" to either allow Roll20 to search for locally installed fonts or import a font from an URL.
  
I am not certain that this trick is recommended by the developers, but as of this writing, it works.  
+
{{repo|roll20-character-sheets/tree/master/Starfinder%20HUD Starfinder HUD}} is an example of a sheet using custom font, and having a fallback font if the user doesn't allow unsafe scripts.
  
You can put the same repeating section name in your html more than once in order to display the same information in multiple styles. You can have individual items displayed one way in one instance of the section (for example as a list of select/options) and a different way (for example as a read only text field) elsewhere. It is extremely useful to combine this trick with the previous section (filtering), so that only a select subset are displayed in different places, but you can do this with the convenience of having the data stored once in one list.
+
<pre data-language="css">
 +
@font-face{/*The aldrich font*/
 +
    font-family:Aldrich;
 +
    src:local('Aldrich'),local('Aldrich-Regular'),url(https://kurohyou.github.io/Starfinder-Character-Sheet/Sheet%20Fonts/Aldrich-Regular.ttf);
 +
}
  
For example, on one tab you can have lots of fiddly options and choices, but on another tab you can display the same list of data pared down to it's minimalist essentials. If the user needs the full list, he can go to the details tab. But the items he uses most frequently fit compactly in a section of another tab. In ether case he is looking at, and manipulating the same list of data, not a list and a separate copy.
+
.charsheet * {
 +
    // sets Aldrich to be used on the whole sheet, & adds "Copperplate" & "Monospace" as backup fonts if Aldrich fails to load.
 +
    font-family:Aldrich,Copperplate,Monospace;
 +
}</pre>
  
Here are the rules:
+
In the segment above, <code>@font-face</code> defines the name and source of the font. It first asks the broswer if the font exists locally, and if it isn't locally installed, the url gives a location to download the url from.
* You can repeat repeating section names. Each repeated section will share the same rows and items, but each can present the data in a different way. For example :     <fieldset class="repeating_weapons sheet-filtershortlist sheet-filtersummarylist">      the additional class tags can be used to differentiate how this section is to be styled as opposed to other repeating_weapons lists display the data.
+
 
* Not all items must be present or displayed in all lists. However each item must be present in every list in which it is used. For example, if you have an auto-calc field, or a button that uses a field, All the fields used in the autocalc, or passed by the button, must be present in the same instance of the repeating section, it will not go looking for it in other instances of the repeating section. The referenced field may be hidden, but must be present.  
+
* [https://www.cssfontstack.com/ cssfontstack.com] is a list of Web-safe fonts that comes preinstalled on most operating systems, which helps avoiding the need to find a source where the font is hosted online.  
* You can change how fields are presented. For example they can be editable in one instance, but readonly in another.
+
* [https://github.com/Roll20/roll20-character-sheets/search?q=ttf&unscoped_q=ttf This search through Roll20's sheet repository] shows other sheet that also use custom fonts.
* If you change repitem to inline-block, it will float "rows" up so that it can display more than one per line.
+
 
<pre data-language="css" style="margin-bottom:5px">
+
==iframe==
fieldset.sheet-filtersummarylist+.repcontainer .repitem {
+
Since mid-2021, you are able to style small parts outside the sheet it self, namely the Bio&Info-tab, A&A-tab, and the buttons to navigate between them.
   display: inline-block;
+
 
   margin-right: 2em;
+
Here is an example that will:
 +
* make the Bio&Info/Character Sheet/A&A buttons stay wisible at the top of your sheet even when you scroll down the sheet
 +
* make the A&A-button slightly less noticable
 +
* minor adjustments on the A&A-tab
 +
<pre data-language="css">
 +
.characterviewer .content {
 +
  margin-bottom: 0;
 +
}
 +
.characterviewer .nav-tabs {
 +
  padding-top: 2px;
 +
  min-width: 351px;
 +
  margin-bottom: 0;
 +
}
 +
.characterviewer .nav-tabs > li:nth-child(2) {
 +
  -webkit-box-ordinal-group: 0;
 +
      -ms-flex-order: -1;
 +
          order: -1;
 +
}
 +
.characterviewer .nav-tabs > li:nth-child(4) > a:nth-child(1) {
 +
  opacity: 0.3;
 +
}
 +
.characterviewer .nav-tabs > li:nth-child(4) > a:nth-child(1):hover, .characterviewer .nav-tabs > li:nth-child(4) > a:nth-child(1):focus {
 +
   opacity: 1;
 +
}
 +
 
 +
.ui-dialog .largedialog {
 +
  padding: 0px 5px;
 +
}
 +
.ui-dialog .charsheet {
 +
   padding: 0;
 +
}
 +
 
 +
div#tab-content.tab-content .attributesabilities {
 +
  min-width: 525px;
 +
}
 +
div#tab-content.tab-content .attributesabilities .largedialog textarea {
 +
  border: 2px solid grey;
 +
  background: #ececec;
 
}
 
}
 
</pre>
 
</pre>
* If you want the user to only add or modify items in one place, you can get rid of the Add/Modify buttons in the other places by simply adding display: none: to a set of tags similar to this: fieldset.sheet-filtershortlist~.repcontrol where of course the .sheet tag is whatever tag you specify in your html.
 
  
== Hide Areas ==
+
=Tricks and Effects=
[http://jsfiddle.net/LTnd7/ Live Demo]
+
Tricks and other more involved methods for create effects on your sheet.
  
You can hide areas on the character sheet based on the state of a checkbox. Instead of the adjacent sibling selector (<code>+</code>) used by [[#Styling Checkboxes and Radio Buttons|custom checkboxes]], you should use the sibling selector (<code>~</code>).
+
== Show/Hide Areas ==
<pre data-language="html" style="margin-bottom: 5px"><div>
+
{{notebox|'''Common mistake:: Not understanding how [https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator General Sibling Selector] <code>~</code> works, and how it applies in making tabs/hideable areas on the sheet.''' The examples on show/hide areas & creating tabs relies on the correct positioning of elements, and if the html elements are thrown in a different order or withing other elements, the conditions aren't met for things to trigger.}}
    <input type="checkbox" class="sheet-arrow">
+
 
    <h4>Stuff and Things</h4>
+
[[File:Charsheet-show-hide-checkbox.gif|500px|thumbnail|right]]
    <div class="sheet-body">
+
[https://jsfiddle.net/8mo7n5qw/ Live Demo]
        <input type="text" placeholder="Name" name="attr_name"><br>
+
 
        <input type="text" placeholder="Description" name="attr_description">
+
You can show or hide areas on the character sheet based on the state of a checkbox. Instead of the adjacent sibling selector (<code>+</code>) used by [[#Styling Checkboxes and Radio Buttons|custom checkboxes]], you should use the sibling selector (<code>~</code>).
        <input type="number" min="0" max="20" value="0" name="attr_level"><br>
+
 
        <textarea placeholder="Fulltext" name="attr_bio"></textarea>
+
On the 'toggle-show' checkbox, the following 'body' is hidden when the toggle is not checked.
    </div>
+
 
 +
On the 'toggle-hide' checkbox, the following 'body' is hidden when the toggle is checked.
 +
<pre data-language="html" >
 +
<div class="sheet-columns">
 +
  <div>
 +
      <input type="checkbox" class="sheet-toggle-show" />
 +
      <span>Show</span>
 +
      <div class="sheet-body">You found me!</div>
 +
  </div>
 +
  <div>
 +
      <input type="checkbox" class="sheet-toggle-hide" />
 +
      <span>Hide</span>
 +
      <div class="sheet-body">Hey, a little privacy here?!</div>
 +
  </div>
 
</div>
 
</div>
<div>
+
</pre>
    <input type="checkbox" class="sheet-arrow">
+
<pre data-language="css" >
    <h4>More Stuff</h4>
+
.charsheet .sheet-columns {
    <div class="sheet-body">
+
  display: flex;
        <input type="text" placeholder="Name" name="attr_alternative-name"><br>
+
  justify-content: space-between;
        <input type="text" placeholder="Description" name="attr_alternative-description">
+
  width: 400px;
        <input type="number" min="0" max="20" value="0" name="attr_alternative-level"><br>
+
}
        <textarea placeholder="Fulltext" name="attr_alternative-bio"></textarea>
+
.charsheet .sheet-columns > * {
    </div>
+
  flex: 1;
</div>
+
<div>
+
    <input type="checkbox" class="sheet-arrow">
+
    <h4>Other Things</h4>
+
    <div class="sheet-body">
+
        <input type="text" placeholder="Name" name="attr_class-name"><br>
+
        <input type="text" placeholder="Description" name="attr_class-description">
+
        <input type="number" min="0" max="20" value="0" name="attr_class-level"><br>
+
        <textarea placeholder="Fulltext" name="class-bio"></textarea>
+
    </div>
+
</div></pre>
+
<pre data-language="css" style="margin-top: 5px">input.sheet-arrow {
+
    float: left;
+
 
}
 
}
  
input.sheet-arrow:checked ~ div.sheet-body {
+
.charsheet input.sheet-toggle-show:not(:checked) ~ div.sheet-body,
 +
.charsheet input.sheet-toggle-hide:checked ~ div.sheet-body {
 
     display: none;
 
     display: none;
}</pre>
+
}
Whenever one of the checkboxes in this example is checked, the following div becomes hidden.
+
</pre>
  
 
=== Swap Visible Areas ===
 
=== Swap Visible Areas ===
 +
[[File:Character-sheets-swap-area.gif|400px|thumbnail|right]]
 
[http://jsfiddle.net/yodb4b5w/ Live Demo]
 
[http://jsfiddle.net/yodb4b5w/ Live Demo]
  
 
You can apply the [[#Hide Areas|hide areas]] logic to multiple elements based on the same checkbox, and get swappable behavior:
 
You can apply the [[#Hide Areas|hide areas]] logic to multiple elements based on the same checkbox, and get swappable behavior:
<pre data-language="html" style="margin-bottom:5px"><div>
+
<pre data-language="html" ><div>
   <input type="checkbox" class="sheet-block-switch">
+
   <input type="checkbox" class="sheet-block-switch" name="attr_block_switch" value="1">
 
   <div class="sheet-block-a">
 
   <div class="sheet-block-a">
 
     Lorem ipsum dolor sit amet
 
     Lorem ipsum dolor sit amet
Line 694: Line 1,177:
 
   </div>
 
   </div>
 
</div></pre>
 
</div></pre>
<pre data-language="css" style="margin-top:5px">.sheet-block-a,
+
<pre data-language="css" >.sheet-block-a,
.sheet-block-switch:checked ~ .sheet-block-b {
+
.charsheet .sheet-block-switch:checked ~ .sheet-block-b {
 
   display: block;
 
   display: block;
 
}
 
}
  
.sheet-block-b,
+
.charsheet .sheet-block-b,
.sheet-block-switch:checked ~ .sheet-block-a {
+
.charsheet .sheet-block-switch:checked ~ .sheet-block-a {
 
   display: none;
 
   display: none;
 
}</pre>
 
}</pre>
Line 706: Line 1,189:
 
=== Tabs ===
 
=== Tabs ===
 
[[File:Tabs-example.gif|500px|thumbnail|right]]
 
[[File:Tabs-example.gif|500px|thumbnail|right]]
This is a short and simple example of implementing tabs on a character sheet, created by [https://app.roll20.net/forum/post/7879593/tabs-on-your-custom-character-sheet/?pageforid=7885202#post-7885202 Finderski & GiGs].
+
Many paper-versions of character sheets have more than one page, and among the best ways to implement this in Roll20 sheets is to create tabs for each page, swapping the visible area with some form of buttons. The following examples are the main two ways this can be done, where the first one is more streamlined than the older example, and is using much more of an modern approach, leading to less problems on older/less used browsers.
  
<pre data-language="html" style="margin-bottom: 5px">
+
'''Button-based tabs'''
 +
 
 +
This is a short and simple example of implementing tabs on a character sheet using [[Button#Action_Button|action buttons]] and sheetworkers, created by [https://app.roll20.net/forum/post/7879593/tabs-on-your-custom-character-sheet/?pageforid=7885202#post-7885202 Finderski & GiGs].
 +
 
 +
<pre data-language="html" >
 +
<input type="hidden" class="sheet-tabstoggle" name="attr_sheetTab"  value="character" />
 
<div>
 
<div>
 
     <button type="action" name="act_character" >Character</button>
 
     <button type="action" name="act_character" >Character</button>
Line 714: Line 1,202:
 
     <button type="action" name="act_configuration" >Configuration</button>
 
     <button type="action" name="act_configuration" >Configuration</button>
 
</div>
 
</div>
<input type='hidden' class='sheet-tabstoggle' name='attr_sheetTab'  value='character'  />
 
  
<div class='sheet-character'>
+
<div class="sheet-character">
 
     <h2>Character</h2>
 
     <h2>Character</h2>
 
     <span>character Stuff Goes here </span>
 
     <span>character Stuff Goes here </span>
 
</div>
 
</div>
<div class='sheet-journal'>
+
<div class="sheet-journal">
 
     <h2>Journal/Notes</h2>
 
     <h2>Journal/Notes</h2>
 
     <span>Journal/Notes Stuff Goes here</span>  
 
     <span>Journal/Notes Stuff Goes here</span>  
 
</div>
 
</div>
<div class='sheet-config'>
+
<div class="sheet-config">
 
     <h2>Config/Settings</h2>
 
     <h2>Config/Settings</h2>
 
     <span>Sheet Config/Settings goes here</span>  
 
     <span>Sheet Config/Settings goes here</span>  
Line 741: Line 1,228:
 
</pre>
 
</pre>
  
<pre data-language="css" style="margin-top: 5px">
+
<pre data-language="css" >
 
/*Configure the tab buttons*/
 
/*Configure the tab buttons*/
.sheet-character,
+
.charsheet .sheet-character,
.sheet-config,
+
.charsheet .sheet-config,
.sheet-journal {
+
.charsheet .sheet-journal {
display: none;
+
    display: none;
 
}
 
}
  
 
/* show the selected tab */
 
/* show the selected tab */
.sheet-tabstoggle[value="character"] ~ div.sheet-character,
+
.charsheet .sheet-tabstoggle[value="character"] ~ div.sheet-character,
.sheet-tabstoggle[value="configuration"] ~ .sheet-config,
+
.charsheet .sheet-tabstoggle[value="configuration"] ~ div.sheet-config,
.sheet-tabstoggle[value="journal"] ~ div.sheet-journal {
+
.charsheet .sheet-tabstoggle[value="journal"] ~ div.sheet-journal {
display: block;
+
    display: block;
 
}
 
}
 
</pre>
 
</pre>
<br>
+
Be warned that the location of the hidden input named attr_sheetTab, relative to the div containing the tabs matters. The hidden input must immediately precede the div containing the tabs as in the example above, otherwise it may not work.
 +
 
 +
===<big>radio input + span .method</big>===
 +
''An Older Tab Example using invisible radio inputs + spans to create the "buttons"''
 +
 
 +
'''Note:''' This example/method of creating tabs on a character sheet does not work out of the box, contains lots of unnecessary code, and is far less intuitive than the new tab example. This example is generally not recommended to use, but is left here just for the sake of continuity, and those who might need troubleshoot their implementation. {{repo|Roll20/roll20-character-sheets/tree/master/Ambition_Avarice Ambition & Avarice}} and {{repo|https://github.com/Roll20/roll20-character-sheets/tree/master/Hc%20Svnt%20Dracones%20Second%20Edition Hc Svnt Dracones 2E}} are two sheets that base their tabs on this older ''"invisible radio input + span"''-method, and both needed manual adjustment on at least the position of the tabs to actually work.
 +
 
 +
'''Old sheets with bad tabs:''' Lastly, a number of older sheet's using a variation of this older "input + span"-method don't render correctly on Firefox, and instead shows just the larger radio buttons, but doesn't show the name of the tab. These currently broken tab-implementations could be changed to use either this working version of the "input + span"-method or to the newer "action button" example.
 +
 
 +
::''I suspect these older "input + span" implementations where broken by some minor change that Firefox did to how their display certain elements, and due to this method being IMO a pretty ugly hack codewise that people wouldn't probably use on normal HTML/CSS, it's understandable our "hack" eventually stopped working.'' [[User:1223200|1223200]] ([[User talk:1223200|talk]]) 12:11, 17 May 2021 (UTC)
 +
 
 
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 
| Old Tab example(Doesn't work as-is, lots of redundant code, not intuitive)
 
| Old Tab example(Doesn't work as-is, lots of redundant code, not intuitive)
 
|-
 
|-
 
|
 
|
'''Note:''' This example/method of creating tabs on a character sheet does not work out of the box, contains lots of unnecessary code, and is far less intuitive than the new tab example. This example is generally not recommended to use, but is left here just for the sake of continuity, and those who might need troubleshoot their implementation. [https://github.com/Roll20/roll20-character-sheets/tree/master/Ambition_Avarice Ambition & Avarice] and [https://github.com/Roll20/roll20-character-sheets/tree/master/Hc%20Svnt%20Dracones%20Second%20Edition Hc Svnt Dracones 2E] are two sheets that base their tabs on this example, and bot needed manual adjustment on at least the position of the tabs to actually work.
 
 
[http://jsfiddle.net/z866duoa/ Live demo]
 
[http://jsfiddle.net/z866duoa/ Live demo]
 
A tabbed layout is essentially an extension of hidden areas, using radio inputs instead of checkbox inputs.
 
A tabbed layout is essentially an extension of hidden areas, using radio inputs instead of checkbox inputs.
Line 789: Line 1,285:
 
     Ut enim ad minim veniam
 
     Ut enim ad minim veniam
 
</div></pre>
 
</div></pre>
<pre data-language="css" style="margin-top: 5px">div.sheet-tab-content { display: none; }
+
<pre data-language="css" >div.sheet-tab-content { display: none; }
  
 
input.sheet-tab1:checked ~ div.sheet-tab1,
 
input.sheet-tab1:checked ~ div.sheet-tab1,
Line 860: Line 1,356:
 
The rest of this example shows off means to make your tabs look pretty, such as using <code>content: attr(title)</code> to set the text of the tab, giving them colors and borders, and even making certain tabs different from the others in some fashion.
 
The rest of this example shows off means to make your tabs look pretty, such as using <code>content: attr(title)</code> to set the text of the tab, giving them colors and borders, and even making certain tabs different from the others in some fashion.
 
|}
 
|}
 +
 +
===Dropdown===
 +
Sometimes it makes more sense to hide/show areas of the sheet with a dropdown(<code><select></code>), especially if there are lots of options, which would make buttons or radio inputs take too much space.
 +
 +
:'''Example:''' If the game ExampleRPG have three classes/archetypes, and each have very different looking sheets compared to the others, it would make sense to have a dropdown which you'd use to change which archetype's section is shown. If you put the whole code into the <code>character</code>-section of the above [[#Tabs|Tabs]] example, you could still have multiple pages for your sheet, where the other pages/tabs are identical, and only some of the content inside the <code>character</code>-page would change depending on the <code><select></code>.
 +
 +
 +
With the <code><select></code> directly changing the values, there is no need for the sheetworker that was used in the [[#Tabs|Tabs]] example.
 +
 +
<pre data-language="html" >
 +
 +
<div>
 +
  <label>Archetype:<label>
 +
  <select name="attr_archetype">
 +
    <option value="fighter" selected>fighter</option>
 +
    <option value="mage">mage</option>
 +
    <option value="rogue">rogue</option>
 +
  </select>
 +
</div>
 +
 +
<div class="character">
 +
//remember that this works only if .sheet-archetypetoggle and .sheet-fighter, .sheet-mage and .sheet-rogue are siblings elements and put in the correct order
 +
<input type="hidden" class="sheet-archetypetoggle" name="attr_archetype"  value="fighter"  />
 +
    <input name="attr_character_name" type="text" /> <!-- name is shown regardless of selected archetype -->
 +
 +
    <div class="sheet-fighter">
 +
        <h2>fighter</h2>
 +
        <span>Fighter weapon specialization here</span>
 +
    </div>
 +
    <div class="sheet-mage">
 +
        <h2>mage</h2>
 +
        <span>Mage spellcasting & magic here</span>
 +
    </div>
 +
    <div class="sheet-rogue">
 +
        <h2>rogue</h2>
 +
        <span>Rogue espertise and skill specialization here</span>
 +
    </div>
 +
</div>
 +
</pre>
 +
 +
<pre data-language="css" >
 +
/* By deafult, hides all archetypes*/
 +
.charsheet .sheet-fighter,
 +
.charsheet .sheet-mage,
 +
.charsheet .sheet-rogue {
 +
    display: none;
 +
}
 +
 +
/* show the selected archetype */
 +
.charsheet .sheet-archetypetoggle[value="fighter"] ~ div.sheet-fighter,
 +
.charsheet .sheet-archetypetoggle[value="mage"] ~ div.sheet-mage,
 +
.charsheet .sheet-archetypetoggle[value="rogue"] ~ div.sheet-rogue {
 +
    display: block;
 +
}
 +
</pre>
 +
 +
===Pop Up Boxes===
 +
With the additional support for <code><a></code> links and IDs using , Roll20 has also given an example of how this functionality can be used to create pop up content using the :target selector.
 +
 +
* [[Character_Sheet_Enhancement#Popup|Character Sheet Enhancement - Popup Example]]
 +
 +
=== Cycling Button ===
 +
It's sometimes useful to have a single control(like button/radio input) that the user can click on to cycle through a series of values/options, instead of creating a <code><select></code>-dropdown.
 +
 +
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
 +
| Old example (pure html+css)
 +
|-
 +
|
 +
[http://jsfiddle.net/ecttk61s/ Live Demo]
 +
 +
You can't make a button that rotates through a list, changing a displayed value. However, you can ''fake'' it!
 +
 +
The trick lies in layering the radio buttons on top of one another, and changing the z-index based on which input is checked.
 +
* First, set all of the inputs to some baseline z-index.
 +
* Set the first radio input of the group to a z-index higher than the rest.
 +
* For each input, when it is checked, set the z-index of the ''next'' input to something higher than the first input. You don't need to do anything for when the last input is selected, as they'll all be back at their default z-index (which means the first one is on top).
 +
 +
When the first input is selected, only the second one will be visible, so you can't click on any other value. When the second is clicked, the third will become the only one you can click on, and so on. The user can also navigate back and forth using arrow keys, or a combination of the tab key (or shift+tab) and the space bar.
 +
<pre data-language="html" ><div class="sheet-damage-box">
 +
    <input type="radio" class="sheet-damage-box sheet-no-damage" name="attr_damage-box" value="0" checked>
 +
    <input type="radio" class="sheet-damage-box sheet-bashing-damage" name="attr_damage-box" value="1">
 +
    <input type="radio" class="sheet-damage-box sheet-lethal-damage" name="attr_damage-box" value="2">
 +
    <input type="radio" class="sheet-damage-box sheet-aggravated-damage" name="attr_damage-box" value="3">
 +
 +
    <span class="sheet-damage-box sheet-no-damage">☐ (no damage)</span>
 +
    <span class="sheet-damage-box sheet-bashing-damage">&amp;nbsp;/&amp;nbsp; (bashing damage)</span>
 +
    <span class="sheet-damage-box sheet-lethal-damage">☓ (lethal damage)</span>
 +
    <span class="sheet-damage-box sheet-aggravated-damage">✱ (aggravated damage)</span>
 +
</div></pre>
 +
<pre data-language="css" >
 +
.charsheet div.sheet-damage-box {
 +
    width: 195px;
 +
    height: 30px;
 +
    position: relative;
 +
}
 +
 +
.charsheet input.sheet-damage-box {
 +
    width: 30px;
 +
    height: 30px;
 +
    position: absolute;
 +
    z-index: 1;
 +
}
 +
 +
.charsheet span.sheet-damage-box {
 +
    margin: 10px 0 0 40px;
 +
    display: none;
 +
}
 +
 +
.charsheet input.sheet-no-damage { z-index: 2; }
 +
 +
.charsheet input.sheet-no-damage:checked + input.sheet-bashing-damage,
 +
.charsheet input.sheet-bashing-damage:checked + input.sheet-lethal-damage,
 +
.charsheet input.sheet-lethal-damage:checked + input.sheet-aggravated-damage { z-index: 3; }
 +
 +
.charsheet input.sheet-no-damage:checked ~ span.sheet-no-damage,
 +
.charsheet input.sheet-bashing-damage:checked ~ span.sheet-bashing-damage,
 +
.charsheet input.sheet-lethal-damage:checked ~ span.sheet-lethal-damage,
 +
.charsheet input.sheet-aggravated-damage:checked ~ span.sheet-aggravated-damage { display: inline-block; }
 +
</pre>
 +
As you can see, this uses the [[#Hide Areas|show/hide areas]] technique to display a span with some text for each radio input. You could also display an image, an input field, an entire section of the charactersheet, whatever you like.
 +
 +
One fancy option would be to combine this with the technique to style your radio buttons. Hide the radios with <code>opacity: 0</code>, and display an image in the same location as the radio button (make sure the image is at a lower z-index than the invisible buttons!) so that the user is apparently clicking on the displayed image to change it.
 +
|}
 +
 +
This uses the same approach described in [[#Checkbox and Radio Input Styling|Checkbox and Radio Input Styling]]: it uses a hidden attribute to store the value and a styled input/button to update the value.
 +
 +
This trick is used on multiple [[WoD]] sheets to cycle through showing different types of damage symbols in their health boxes.
 +
 +
The key distinction here is incrementing the numeric value by 1 each time the button is clicked and using the [https://www.computerhope.com/jargon/m/modulo.htm Modulo Operator] to cycle the value back to 0 once it reaches 4.  This makes it cycle through 0, 1, 2, and 3 before going back to 0.
 +
 +
<pre data-language="html" >
 +
<div>
 +
  <input type="hidden" name="attr_health_1" class="sheet-health" value="0" />
 +
  <button type="action" name="act_health_1_cycle" class="sheet-health"></button>
 +
 +
  <span class="sheet-no-damage">☐ (no damage)</span>
 +
  <span class="sheet-bashing-damage">&nbsp;/&nbsp; (bashing damage)</span>
 +
  <span class="sheet-lethal-damage">☓ (lethal damage)</span>
 +
  <span class="sheet-aggravated-damage">✱ (aggravated damage)</span>
 +
</div>
 +
<script type="text/worker">
 +
  on(`clicked:health_1_cycle`, () => {
 +
      // Check the current value of the hidden attribute.
 +
    getAttrs(["health_1"], (v) => {
 +
      const healthValue = parseInt(v["health_1"]) || 0;
 +
      // Increment the attribute value by 1, or cycle back to 0 if the incremented value is equal to 4.
 +
      setAttrs({
 +
        "health_1": (healthValue + 1) % 4
 +
      });
 +
    });
 +
  });
 +
</script>
 +
</pre>
 +
<pre data-language="css" >
 +
/* Configure the button styling. This example makes it look like a checkbox. */
 +
.charsheet button.sheet-health {
 +
  width: 10px;
 +
  height: 10px;
 +
  padding: 0;
 +
  border: solid 1px #000000;
 +
  background: #efefef;
 +
}
 +
 +
/* Draw a diagonal slash if the hidden attribute value is "1" */
 +
.charsheet input.sheet-health[value="1"] + button.sheet-health {
 +
  background:
 +
    linear-gradient(to top left,
 +
      #efefef 0%,
 +
      #efefef calc(50% - 0.8px),
 +
      rgba(0,0,0,1) 50%,
 +
      #efefef calc(50% + 0.8px),
 +
      #efefef 100%);
 +
}
 +
/* Draw two diagonal slashes if the hidden attribute value is "2" */
 +
.charsheet input.sheet-health[value="2"] + button.sheet-health {
 +
  background:
 +
    linear-gradient(to top left,
 +
      rgba(0,0,0,0) 0%,
 +
      rgba(0,0,0,0) calc(50% - 0.8px),
 +
      rgba(0,0,0,1) 50%,
 +
      rgba(0,0,0,0) calc(50% + 0.8px),
 +
      rgba(0,0,0,0) 100%),
 +
    linear-gradient(to top right,
 +
      #efefef 0%,
 +
      #efefef calc(50% - 0.8px),
 +
      rgba(0,0,0,1) 50%,
 +
      #efefef calc(50% + 0.8px),
 +
      #efefef 100%);
 +
}
 +
/* Draw three diagonal slashes if the hidden attribute value is "3" */
 +
.charsheet input.sheet-health[value="3"] + button.sheet-health {
 +
  background:
 +
    linear-gradient(to top left,
 +
      rgba(0,0,0,0) 0%,
 +
      rgba(0,0,0,0) calc(50% - 0.8px),
 +
      rgba(0,0,0,1) 50%,
 +
      rgba(0,0,0,0) calc(50% + 0.8px),
 +
      rgba(0,0,0,0) 100%),
 +
    linear-gradient(to top right,
 +
      rgba(0,0,0,0) 0%,
 +
      rgba(0,0,0,0) calc(50% - 0.8px),
 +
      rgba(0,0,0,1) 50%,
 +
      rgba(0,0,0,0) calc(50% + 0.8px),
 +
      rgba(0,0,0,0) 100%),
 +
    linear-gradient(to right,
 +
      #efefef 0%,
 +
      #efefef calc(50% - 0.8px),
 +
      rgba(0,0,0,1) 50%,
 +
      #efefef calc(50% + 0.8px),
 +
      #efefef 100%);
 +
}
 +
 +
/* Hide content that does not correspond with the hidden attribute's value. */
 +
.charsheet input.sheet-health[value="0"] ~ :not(.sheet-health):not(.sheet-no-damage) {
 +
  display: none;
 +
}
 +
.charsheet input.sheet-health[value="1"] ~ :not(.sheet-health):not(.sheet-bashing-damage) {
 +
  display: none;
 +
}
 +
.charsheet input.sheet-health[value="2"] ~ :not(.sheet-health):not(.sheet-lethal-damage) {
 +
  display: none;
 +
}
 +
.charsheet input.sheet-health[value="3"] ~ :not(.sheet-health):not(.sheet-aggravated-damage) {
 +
  display: none;
 +
}
 +
</pre>
 +
 +
 +
==Reset Styles==
 +
[https://app.roll20.net/forum/permalink/10469264/ example] by [[Scott]]
 +
 +
Doing this or similar will remove all or most of Roll20's default styling applied to these, so you can avoid more unwanted behavior when you write your own css
 +
 +
:''"You can use unset instead of initial to really unset it, but that's a little harder than I like because you wind up with radios and checkboxes with appearance:none on them, which is useful sometimes, but other times not so much."'' - Scott
 +
<pre data-language="css">
 +
.ui-dialog .charsheet select,
 +
.ui-dialog .charsheet textarea,
 +
.ui-dialog .charsheet input,
 +
.ui-dialog .charsheet .uneditable-input,
 +
.ui-dialog .charsheet label,
 +
.ui-dialog .charsheet button{
 +
  all: initial;
 +
}
 +
</pre>
 +
 +
==Disable Click==
 +
[[File:BCS-clicker-events.gif|right|thumbnail|700px|Example of some inputs disabled with <code>pointer-events: none;</code>(visual glitch unrelated)]]
 +
You can use [https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events pointer-events] or [https://developer.mozilla.org/en-US/docs/Web/CSS/user-select user-select] to disable the user from selecting/interacting an element on the sheet with their mouse. It works for at least <code><select></code>, <code>text/number/checkbox/radio inputs</code> & <code><button></code>.
 +
 +
'''Note:''' Neither <code>pointer-events</code> nor <code>user-select</code> stops users from selecting the element using the ''TAB''-key.
 +
 +
<pre data-language="css">
 +
.sheet-noclick{
 +
  pointer-events: none; 
 +
}
 +
</pre>
 +
 +
Then you can use some other condition to turn this feature on/off, such as a checkbox. This trick have been used on [https://www.github.com/Roll20/roll20-character-sheets/search?p=3&q=pointer-events%3A+none%3B many sheet].
 +
 +
'''Example:'''
 +
 +
<pre data-language="html">
 +
<div>
 +
    <label>disabled</label>
 +
    <span>
 +
        <input type="text" name="attr_name" class="sheet-noclick">
 +
    </span>
 +
    <span>
 +
        <input type="number" name="attr_lvl" class="sheet-noclick">
 +
    </span>
 +
    <span>
 +
        <input type="checkbox" name="attr_check" value="1" class="sheet-noclick">
 +
    </span>
 +
    <span>
 +
        <input type="radio" name="attr_radio" value="0" class="sheet-noclick">
 +
        <input type="radio" name="attr_radio" value="1" class="sheet-noclick">
 +
    </span>
 +
    <select name="attr_WoundLevel" class="sheet-noclick">
 +
      <option value="0" selected="selected">Healthy</option>
 +
      <option value="1">Stunned</option>
 +
      <option value="1">Wounded</option>
 +
      <option value="2">Wounded Twice</option>
 +
      <option value="5">Incapacitated</option>
 +
      <option value="10">Mortally Wounded</option>
 +
    </select>
 +
    <button type="roll" value="/roll 1d20" name="roll_init" class="sheet-noclick">init</button>
 +
</div>
 +
<div>
 +
    <label>normal</label>
 +
    <span>
 +
        <input type="text" name="attr_name">
 +
    </span>
 +
    <span>
 +
        <input type="number" name="attr_lvl">
 +
    </span>
 +
    <span>
 +
        <input type="checkbox" name="attr_check" value="1">
 +
    </span>
 +
    <span>
 +
        <input type="radio" name="attr_radio" value="0">
 +
        <input type="radio" name="attr_radio" value="1">
 +
    </span>
 +
    <select name="attr_class">
 +
        <option value="0" selected="selected">Warrior</option>
 +
        <option value="1">Mage</option>
 +
        <option value="2">Expert</option>
 +
    </select>
 +
    <button type="roll" value="/roll 1d20" name="roll_init">init</button>
 +
</div>
 +
</pre>
 +
 +
<pre data-language="css">
 +
.charsheet select.sheet-noclick,
 +
.charsheet input[type="text"].sheet-noclick,
 +
.charsheet input[type="number"].sheet-noclick,
 +
.charsheet input[type="checkbox"].sheet-noclick,
 +
.charsheet input[type="radio"].sheet-noclick,
 +
.charsheet input[type="text"].sheet-noclick,
 +
.charsheet button[type="roll"].sheet-noclick{
 +
  pointer-events: none; 
 +
}
 +
</pre>
 +
 +
== Dark/Light Mode==
 +
You can create an option for your sheet that can change the full color scheme of the sheet. The "[[Savage Worlds Tabbed]]" and "[[GURPS]]" sheet are two examples that have these options.
 +
 +
* [[GiGs]] have made a [https://app.roll20.net/forum/permalink/9405453/ short example] of this.
 +
* With [[CSE]], it's possible to have dark/light mode be automatically applied, based on the user's computer's light/dark preferences.
 +
** [https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme prefers-color-scheme light/dark]
 +
** works on desktop, doesn't seem to work on mobile''(Oct 2021 [[Andreas J.]] )''
 +
 +
== Custom Progress Bar ==
 +
To create a custom progress bar, one can use CSS and sheetworkers to update an hidden field. See also: {{fpl|9087213/ Creating a progress bar/health bar on the Character Sheet}} for more examples.
 +
=== Example 1 ===
 +
'''[[Andreas J.]]'s example:''' [[File:Sheet-hp-bar.gif|right|thumbnail|300px]]
 +
<pre data-language="html" >
 +
<div class="health-bar">
 +
  <span>HP:</span>
 +
  <input name="attr_hp" class="rangetest" type="range" min="0" max="10" value="10">
 +
  <input name="attr_hp" class="rangetest" type="number" value="10">
 +
</div>
 +
 +
 +
<pre data-language="css" >
 +
input[type="range"].sheet-rangetest{
 +
  width: 100px;
 +
}
 +
/* works only on firefox atm */
 +
input[type="range"].sheet-rangetest::-moz-range-thumb{
 +
  background: transparent;
 +
  border-color: transparent;
 +
  color: transparent;
 +
}
 +
input[type="range"].sheet-rangetest::-moz-range-progress{
 +
  background: green;
 +
  height: 10px;
 +
}
 +
input[type="range"].sheet-rangetest::-moz-range-track{
 +
  background: red;
 +
  height: 10px;
 +
}
 +
</pre>
 +
 +
=== Example 2 ===
 +
'''[[Leothedino]]'s example''' – [https://jsfiddle.net/32gt5cvc/ Live Demo]
 +
 +
Leothedino have {{forum|/permalink/9497799/ created a more sophisticated progress bar}}, where you can change the current and max value for what is displayed, and a sheetworker then scales the numbers to fit the bar.
 +
 +
<pre data-language="html" >
 +
<div class="sheet-container">
 +
<input type="hidden" value="2" name="attr_Character_overweight" class="sheet-hidden sheet-overweight">
 +
<div class="sheet-overweight"></div>
 +
</div>
 +
</pre>
 +
 +
<pre data-language="css" >
 +
div.sheet-overweight {
 +
    width: 90%;
 +
    height: 20px;
 +
    border: 1px solid black;
 +
    color: black;
 +
    text-align: center;
 +
}
 +
input.sheet-overweight[value="0"] ~ div.sheet-overweight {
 +
    background: white;
 +
}
 +
input.sheet-overweight[value="1"] ~ div.sheet-overweight {
 +
    background: linear-gradient(to left, white 60%, green 75%);   
 +
}
 +
input.sheet-overweight[value="2"] ~ div.sheet-overweight {
 +
    background: linear-gradient(to left, white 40%, yellow 75%);
 +
}
 +
    input.sheet-overweight[value="2"] ~ div.sheet-overweight:before {
 +
        content:"Bags half full";
 +
    }
 +
input.sheet-overweight[value="3"] ~ div.sheet-overweight {
 +
    background: linear-gradient(to left, white 20%, orange 75%);
 +
}
 +
    input.sheet-overweight[value="3"] ~ div.sheet-overweight:before {
 +
        content: "Bags nearly full";
 +
    }
 +
input.sheet-overweight[value="4"] ~ div.sheet-overweight {
 +
    background: linear-gradient(to left, white, red 100%);
 +
}
 +
    input.sheet-overweight[value="4"] ~ div.sheet-overweight:before {
 +
        content: "Bags full !";
 +
    }
 +
input.sheet-overweight[value="5"] ~ div.sheet-overweight {
 +
    background: black;
 +
    color: white;
 +
}
 +
    input.sheet-overweight[value="5"] ~ div.sheet-overweight:before {
 +
        content: "Bags too heavy !";
 +
}</pre>
 +
 +
=== Example 3 ===
 +
'''[[Scott C.]]'s example:''' [[File:Progress_bar.gif|right|thumbnail|300px]]
 +
Scott has created another sophisticated progress bar {{forum|/permalink/11587794/ here}}.
 +
 +
This looks pretty complicated, but the benefit is that we can use this styling and sheetworker for any progress bar setup we might make and don't need to redo the CSS or javascript for specific steps, attribute names, or maximum values. Additionally, if you're using a templating library like PUG, Mustache, or handlebars to build the html of your sheet, this setup can be built via a bit of resuable code instead of needing to copy/paste the html each time you need it.
  
 
== Hexagons ==
 
== Hexagons ==
 
[http://jsfiddle.net/herrozerro/A26S9/3/ Live demo]
 
[http://jsfiddle.net/herrozerro/A26S9/3/ Live demo]
  
<pre data-language="html" style="margin-bottom: 5px"><div class="sheet-hex sheet-hex-3" style="background-color: #444; width: 100px; height: 57px">
+
<pre data-language="html" >
 +
<div class="sheet-hex sheet-hex-3" style="background-color: #444; width: 100px; height: 57px">    
 
     <div class="sheet-inner">
 
     <div class="sheet-inner">
 
         <h4>Stat</h4>
 
         <h4>Stat</h4>
 
         <input type="number" style="width: 50%">
 
         <input type="number" style="width: 50%">
     </div>
+
     </div>    
 
     <div class="sheet-corner-1"></div>
 
     <div class="sheet-corner-1"></div>
     <div class="sheet-corner-2"></div>
+
     <div class="sheet-corner-2"></div>    
 
</div></pre>
 
</div></pre>
<pre data-language="css" style="margin-top: 5px">.sheet-hex {
+
<pre data-language="css" >
 +
.sheet-hex {
 
     width: 100px;
 
     width: 100px;
 
     height: 57px;
 
     height: 57px;
 
     background-color: #ccc;
 
     background-color: #ccc;
 
     background-repeat: no-repeat;
 
     background-repeat: no-repeat;
     background-position: 50% 50%;
+
     background-position: 50% 50%;          
     background-size: auto 173px;
+
     background-size: auto 173px;                          
 
     position: relative;
 
     position: relative;
 
     float: left;
 
     float: left;
Line 907: Line 1,825:
 
     width: 100%;
 
     width: 100%;
 
     height: 100%;
 
     height: 100%;
     background: inherit;
+
     background: inherit;                              
     z-index: -2;
+
     z-index: -2;                      
     overflow: hidden;
+
     overflow: hidden;      
     backface-visibility: hidden;
+
     backface-visibility: hidden;          
 
}
 
}
  
Line 934: Line 1,852:
 
     background: inherit;
 
     background: inherit;
 
     background-repeat: no-repeat;
 
     background-repeat: no-repeat;
     backface-visibility: hidden;  
+
     backface-visibility: hidden;                
 
}
 
}
  
 
.sheet-hex .sheet-corner-1::before {
 
.sheet-hex .sheet-corner-1::before {
     transform: rotate(-60deg) translate(-87px, 0px);
+
     transform: rotate(-60deg) translate(-87px, 0px);  
 
     transform-origin: 0 0;
 
     transform-origin: 0 0;
 
}
 
}
  
 
.sheet-hex .sheet-corner-2::before {
 
.sheet-hex .sheet-corner-2::before {
     transform: rotate(60deg) translate(-48px, -11px);
+
     transform: rotate(60deg) translate(-48px, -11px);  
 
     bottom: 0;
 
     bottom: 0;
 
}
 
}
Line 951: Line 1,869:
  
 
.sheet-hex h4 {
 
.sheet-hex h4 {
     font-family: 'Josefin Sans', sans-serif;
+
     font-family: 'Josefin Sans', sans-serif;      
     margin: 0;
+
     margin: 0;        
 
}
 
}
  
Line 989: Line 1,907:
 
   <div class="sheet-clock"></div>
 
   <div class="sheet-clock"></div>
 
</div></pre>
 
</div></pre>
<pre data-language="css" style="margin-top:5px">.sheet-harm-section {
+
<pre data-language="css" >.sheet-harm-section {
 
     display: inline-block;
 
     display: inline-block;
 
     text-align: center;
 
     text-align: center;
Line 1,035: Line 1,953:
 
}</pre>
 
}</pre>
  
== Cycling Button ==
 
[http://jsfiddle.net/ecttk61s/ Live Demo]
 
  
You can't make a button that rotates through a list, changing a displayed value. However, you can ''fake'' it!
 
  
The trick lies in layering the radio buttons on top of one another, and changing the z-index based on which input is checked.
+
=Special=
* First, set all of the inputs to some baseline z-index.
+
=== Icon Fonts ===
* Set the first radio input of the group to a z-index higher than the rest.
+
An icon font is a font which has pictures instead of letters, and Roll20 have a number of them accessible by default. You can specify one of the icon fonts below with the <code>font-family</code> property. For example, something like:
* For each input, when it is checked, set the z-index of the ''next'' input to something higher than the first input. You don't need to do anything for when the last input is selected, as they'll all be back at their default z-index (which means the first one is on top).
+
<pre data-language="html">
 +
<p>A Gem: <span style="font-family: 'Pictos Three'">a</span></p>
 +
</pre>
 +
Would produce:
 +
:<p>A Gem: <span style="font-family: 'Pictos Three'">a</span></p>
  
When the first input is selected, only the second one will be visible, so you can't click on any other value. When the second is clicked, the third will become the only one you can click on, and so on. The user can also navigate back and forth using arrow keys, or a combination of the tab key (or shift+tab) and the space bar.
+
{{ex}}
<pre data-language="html" style="margin-bottom:5px"><div class="sheet-damage-box">
+
<pre data-language="css">
    <input type="radio" class="sheet-damage-box sheet-no-damage" name="attr_damage-box" value="0" checked>
+
.charsheet div.sheet-d6{
    <input type="radio" class="sheet-damage-box sheet-bashing-damage" name="attr_damage-box" value="1">
+
  font-family: dicefontd6;
    <input type="radio" class="sheet-damage-box sheet-lethal-damage" name="attr_damage-box" value="2">
+
  // makes the content of this div a black d6 showing 6 dots, by defining the character that corresponds to that symbol.
    <input type="radio" class="sheet-damage-box sheet-aggravated-damage" name="attr_damage-box" value="3">
+
  content: "L";
 
+
    <span class="sheet-damage-box sheet-no-damage">☐ (no damage)</span>
+
    <span class="sheet-damage-box sheet-bashing-damage">&amp;nbsp;/&amp;nbsp; (bashing damage)</span>
+
    <span class="sheet-damage-box sheet-lethal-damage">☓ (lethal damage)</span>
+
    <span class="sheet-damage-box sheet-aggravated-damage">✱ (aggravated damage)</span>
+
</div></pre>
+
<pre data-language="css" style="margin-top:5px">div.sheet-damage-box {
+
    width: 195px;
+
    height: 30px;
+
    position: relative;
+
 
}
 
}
 
+
.charsheet div.sheet-d8{
input.sheet-damage-box {
+
  // any div with this class will display the test as various d8 dice
    width: 30px;
+
  font-family: dicefontd8;
    height: 30px;
+
    position: absolute;
+
    z-index: 1;
+
 
}
 
}
 +
</pre>
  
span.sheet-damage-box {
+
<pre data-language="html">
    margin: 10px 0 0 40px;
+
<div>
    display: none;
+
<p>At character creation, you have a dicepool of:</p>
}
+
<p>5 <div class="sheet-d6">I</div> </p>
 +
<p>3 <div class="sheet-d8">h</div> </p>
 +
<p> bunch of d8 with different numbers<div class="sheet-d8">abcdefgh</div>
 +
</div>
 +
</pre>
 +
Looks about:
  
input.sheet-no-damage { z-index: 2; }
+
<p>At character creation, you have a dicepool of:</p>
 
+
  <p>5 <div style="font-family: dicefontd6;">I</div> </p>
input.sheet-no-damage:checked + input.sheet-bashing-damage,
+
<p>3 <div style="font-family: dicefontd8;">h</div> </p>
input.sheet-bashing-damage:checked + input.sheet-lethal-damage,
+
<p> bunch of d8 with different numbers<div style="font-family: dicefontd8;">abcdefgh</div>
input.sheet-lethal-damage:checked + input.sheet-aggravated-damage { z-index: 3; }
+
 
+
input.sheet-no-damage:checked ~ span.sheet-no-damage,
+
input.sheet-bashing-damage:checked ~ span.sheet-bashing-damage,
+
input.sheet-lethal-damage:checked ~ span.sheet-lethal-damage,
+
input.sheet-aggravated-damage:checked ~ span.sheet-aggravated-damage { display: inline-block; }</pre>
+
As you can see, this uses the [[#Hide Areas|show/hide areas]] technique to display a span with some text for each radio input. You could also display an image, an input field, an entire section of the charactersheet, whatever you like.
+
 
+
One fancy option would be to combine this with the technique to style your radio buttons. Hide the radios with <code>opacity: 0</code>, and display an image in the same location as the radio button (make sure the image is at a lower z-index than the invisible buttons!) so that the user is apparently clicking on the displayed image to change it.
+
 
+
 
+
== Sheet Settings ==
+
 
+
Many official sheets like [https://wiki.roll20.net/GUMSHOE_Official_Sheet GUMSHOE] have a "Sheet Settings" that can be accessed through a cog icon, which have many options to introduce changes to the sheet at will.
+
 
+
The options are [https://github.com/Roll20/roll20-character-sheets/blob/master/Gumshoe%20Official/sheet.json documented] in the game's .json file under "useroptions" tag and the rest of course in html/css.
+
 
+
More info can be found in [https://app.roll20.net/forum/post/4364848/slug%7D this forum post]
+
 
+
== Custom Fonts ==
+
Roll20 supports five fonts (Arial, Patrick Hand, Contrail One, Shadows Into Light and Candal). It's still possible to use custom fonts, but it's not guaranteed to work and requires the user who want to see them to disable browser brower security to allow "unsecure scripts" to either allow Roll20 to search for locally installed fonts or import a font from an URL.
+
 
+
[https://github.com/Roll20/roll20-character-sheets/tree/master/Starfinder%20HUD Starfinder HUD] is an example of a sheet using custom font, and having a fallback font if the user doesn't allow unsafe scripts.
+
 
+
<pre>@font-face{/*The aldrich font*/
+
    font-family:Aldrich;
+
    src:local('Aldrich'),local('Aldrich-Regular'),url(https://kurohyou.github.io/Starfinder-Character-Sheet/Sheet%20Fonts/Aldrich-Regular.ttf);
+
}
+
 
+
* {
+
    font-family:Aldrich,Copperplate,Monospace;
+
}</pre>
+
 
+
In the segment above, <code>@font-face</code> defines the name and source of the font. It first asks the broswer if the font exists locally, and if it isn't locally installed, the url gives a location to download the url from.
+
 
+
[https://www.cssfontstack.com/ Here] is a list of Web-safe fonts that comes preinstalled on most operating systems, which helps avoiding the need to find a source where the font is hosted online.
+
[https://github.com/Roll20/roll20-character-sheets/search?q=ttf&unscoped_q=ttf This search through Roll20's sheet repository] shows other sheet that also use custom fonts.
+
 
+
== Icon Fonts ==
+
An icon font is a font which has pictures instead of letters. You can specify one of the icon fonts below with the <code>font-family</code> property. For example, something like:
+
<pre data-language="html" style="margin-bottom:5px"><p>A Gem: <span style="font-family: 'Pictos Three'">a</span></p></pre>
+
Would produce:
+
:<p>A Gem: <span style="font-family: 'Pictos Three'">a</span></p>
+
  
=== Pictos ===
+
==== Pictos ====
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
! Character !! Icon !! !! Character !! Icon !! !! Character !! Icon !! !! Character !! Icon
+
! Character !! Icon !! style="background:black;width:2px;padding:0px;" | !! Character !! Icon !! style="background:black;width:2px;padding:0px;"| !! Character !! Icon !! style="background:black;width:2px;padding:0px;" | !! Character !! Icon
 
|-
 
|-
 
{{icon character|Pictos|!}}
 
{{icon character|Pictos|!}}
||{{icon character|Pictos|:}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|S}}
+
{{icon character|Pictos|:}}
||{{icon character|Pictos|l}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|S}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|l}}
 
|-
 
|-
 
{{icon character|Pictos|"}}
 
{{icon character|Pictos|"}}
||{{icon character|Pictos|;}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|T}}
+
{{icon character|Pictos|;}}
||{{icon character|Pictos|m}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|T}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|m}}
 
|-
 
|-
 
{{icon character|Pictos|#}}
 
{{icon character|Pictos|#}}
||{{icon character|Pictos|<}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|U}}
+
{{icon character|Pictos|<}}
||{{icon character|Pictos|n}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|U}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|n}}
 
|-
 
|-
 
{{icon character|Pictos|$}}
 
{{icon character|Pictos|$}}
||{{icon character|Pictos|2==}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|V}}
+
{{icon character|Pictos|2==}}
||{{icon character|Pictos|o}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|V}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|o}}
 
|-
 
|-
 
{{icon character|Pictos|%}}
 
{{icon character|Pictos|%}}
||{{icon character|Pictos|>}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|W}}
+
{{icon character|Pictos|>}}
||{{icon character|Pictos|p}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|W}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|p}}
 
|-
 
|-
 
{{icon character|Pictos|&}}
 
{{icon character|Pictos|&}}
||{{icon character|Pictos|?}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|X}}
+
{{icon character|Pictos|?}}
||{{icon character|Pictos|q}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|X}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|q}}
 
|-
 
|-
 
{{icon character|Pictos|'}}
 
{{icon character|Pictos|'}}
||{{icon character|Pictos|@}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|Y}}
+
{{icon character|Pictos|@}}
||{{icon character|Pictos|r}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|Y}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|r}}
 
|-
 
|-
 
{{icon character|Pictos|(}}
 
{{icon character|Pictos|(}}
||{{icon character|Pictos|A}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|Z}}
+
{{icon character|Pictos|A}}
||{{icon character|Pictos|s}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|Z}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|s}}
 
|-
 
|-
 
{{icon character|Pictos|)}}
 
{{icon character|Pictos|)}}
||{{icon character|Pictos|B}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|[}}
+
{{icon character|Pictos|B}}
||{{icon character|Pictos|t}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|[}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|t}}
 
|-
 
|-
 
{{icon character|Pictos|*}}
 
{{icon character|Pictos|*}}
||{{icon character|Pictos|C}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|\}}
+
{{icon character|Pictos|C}}
||{{icon character|Pictos|u}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|\}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|u}}
 
|-
 
|-
 
{{icon character|Pictos|+}}
 
{{icon character|Pictos|+}}
||{{icon character|Pictos|D}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|]}}
+
{{icon character|Pictos|D}}
||{{icon character|Pictos|v}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|]}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|v}}
 
|-
 
|-
 
{{icon character|Pictos|,}}
 
{{icon character|Pictos|,}}
||{{icon character|Pictos|E}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|^}}
+
{{icon character|Pictos|E}}
||{{icon character|Pictos|w}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|^}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|w}}
 
|-
 
|-
 
{{icon character|Pictos|-}}
 
{{icon character|Pictos|-}}
||{{icon character|Pictos|F}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|_}}
+
{{icon character|Pictos|F}}
||{{icon character|Pictos|x}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|_}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|x}}
 
|-
 
|-
 
{{icon character|Pictos|.}}
 
{{icon character|Pictos|.}}
||{{icon character|Pictos|G}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|`}}
+
{{icon character|Pictos|G}}
||{{icon character|Pictos|y}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|`}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|y}}
 
|-
 
|-
 
{{icon character|Pictos|/}}
 
{{icon character|Pictos|/}}
||{{icon character|Pictos|H}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|a}}
+
{{icon character|Pictos|H}}
||{{icon character|Pictos|z}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|a}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|z}}
 
|-
 
|-
 
{{icon character|Pictos|0}}
 
{{icon character|Pictos|0}}
||{{icon character|Pictos|I}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|b}}
+
{{icon character|Pictos|I}}
||{{icon character|Pictos|{}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|b}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|{}}
 
|-
 
|-
 
{{icon character|Pictos|1}}
 
{{icon character|Pictos|1}}
||{{icon character|Pictos|J}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|c}}
+
{{icon character|Pictos|J}}
||{{icon character|Pictos|{{!}}}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|c}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|{{!}}}}
 
|-
 
|-
 
{{icon character|Pictos|2}}
 
{{icon character|Pictos|2}}
||{{icon character|Pictos|K}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|d}}
+
{{icon character|Pictos|K}}
||{{icon character|Pictos|{{)}}}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|d}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|{{)}}}}
 
|-
 
|-
 
{{icon character|Pictos|3}}
 
{{icon character|Pictos|3}}
||{{icon character|Pictos|L}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|e}}
+
{{icon character|Pictos|L}}
||{{icon character|Pictos|~}}
+
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|e}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|~}}
 
|-
 
|-
 
{{icon character|Pictos|4}}
 
{{icon character|Pictos|4}}
||{{icon character|Pictos|M}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|f}}
+
{{icon character|Pictos|M}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|f}}
 
|-
 
|-
 
{{icon character|Pictos|5}}
 
{{icon character|Pictos|5}}
||{{icon character|Pictos|N}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|g}}
+
{{icon character|Pictos|N}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|g}}
 
|-
 
|-
 
{{icon character|Pictos|6}}
 
{{icon character|Pictos|6}}
||{{icon character|Pictos|O}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|h}}
+
{{icon character|Pictos|O}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|h}}
 
|-
 
|-
 
{{icon character|Pictos|7}}
 
{{icon character|Pictos|7}}
||{{icon character|Pictos|P}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|i}}
+
{{icon character|Pictos|P}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|i}}
 
|-
 
|-
 
{{icon character|Pictos|8}}
 
{{icon character|Pictos|8}}
||{{icon character|Pictos|Q}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|j}}
+
{{icon character|Pictos|Q}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|j}}
 
|-
 
|-
 
{{icon character|Pictos|9}}
 
{{icon character|Pictos|9}}
||{{icon character|Pictos|R}}
+
| style="background:black;width:2px;padding:0px;" |
||{{icon character|Pictos|k}}
+
{{icon character|Pictos|R}}
 +
| style="background:black;width:2px;padding:0px;" |
 +
{{icon character|Pictos|k}}
 
|}
 
|}
 
+
<br><br>
=== Pictos Custom ===
+
<div style="column-count: 3;">
 +
==== Pictos Custom ====
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,276: Line 2,215:
 
|-
 
|-
 
{{icon character|Pictos Custom|y}}
 
{{icon character|Pictos Custom|y}}
|}
+
|}<br><br><br><br><br><br><br><br><br><br><br>
  
=== Pictos Three ===
+
<h4 style="margin-top: 0px;padding-top:0.85em;"> Pictos Three </h4>
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,306: Line 2,245:
 
|-
 
|-
 
{{icon character|Pictos Three|l}}
 
{{icon character|Pictos Three|l}}
|}
+
|}<br><br><br><br><br><br><br><br><br><br><br>
  
=== dicefontd4 ===
+
<h4 style="margin-top: 0px;padding-top:0.85em;"> fontello </h4>
 +
{| class="wikitable" style="text-align:center"
 +
|-
 +
! Character !! Icon
 +
|-
 +
{{icon character extended|fontello|&#xe800;|&amp;#xe800;}}
 +
|-
 +
{{icon character extended|fontello|&#xe801;|&amp;#xe801;}}
 +
|-
 +
{{icon character extended|fontello|&#xe802;|&amp;#xe802;}}
 +
|-
 +
{{icon character extended|fontello|&#xe803;|&amp;#xe803;}}
 +
|-
 +
{{icon character extended|fontello|&#xe804;|&amp;#xe804;}}
 +
|-
 +
{{icon character extended|fontello|&#xe805;|&amp;#xe805;}}
 +
|-
 +
{{icon character extended|fontello|&#xe806;|&amp;#xe806;}}
 +
|-
 +
{{icon character extended|fontello|&#xe807;|&amp;#xe807;}}
 +
|-
 +
{{icon character extended|fontello|&#xe808;|&amp;#xe808;}}
 +
|-
 +
{{icon character extended|fontello|&#xe809;|&amp;#xe809;}}
 +
|-
 +
{{icon character extended|fontello|&#xe80a;|&amp;#xe80a;}}
 +
|-
 +
{{icon character extended|fontello|&#xe80b;|&amp;#xe80b;}}
 +
|-
 +
{{icon character extended|fontello|&#xe80c;|&amp;#xe80c;}}
 +
|-
 +
{{icon character extended|fontello|&#xe80d;|&amp;#xe80d;}}
 +
|-
 +
{{icon character extended|fontello|&#xe80e;|&amp;#xe80e;}}
 +
|-
 +
{{icon character extended|fontello|&#xe80f;|&amp;#xe80f;}}
 +
|-
 +
{{icon character extended|fontello|&#xe810;|&amp;#xe810;}}
 +
|-
 +
{{icon character extended|fontello|&#xe811;|&amp;#xe811;}}
 +
|-
 +
{{icon character extended|fontello|&#xe812;|&amp;#xe812;}}
 +
|-
 +
{{icon character extended|fontello|&#xe813;|&amp;#xe813;}}
 +
|-
 +
{{icon character extended|fontello|&#xf008;|&amp;#xf008;}}
 +
|}
 +
</div>
 +
<br><br>
 +
<div style="column-count: 3;">
 +
<h4 style="margin-top: 0px;padding-top:0.85em;"> dicefontd4 </h4>
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,365: Line 2,354:
 
{{icon character|dicefontd4|P|font-size:200%}}
 
{{icon character|dicefontd4|P|font-size:200%}}
 
|}
 
|}
 
+
<h4 style="margin-top: 0px;padding-top:0.85em;"> dicefontd6 </h4>
=== dicefontd6 ===
+
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,428: Line 2,416:
 
|-
 
|-
 
{{icon character|dicefontd6|R}}
 
{{icon character|dicefontd6|R}}
|}
+
|}<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
  
=== dicefontd8 ===
+
<h4 style="margin-top: 0px;padding-top:0.85em;"> dicefontd8 </h4>
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,463: Line 2,451:
 
{{icon character|dicefontd8|H|font-size:200%}}
 
{{icon character|dicefontd8|H|font-size:200%}}
 
|}
 
|}
 +
</div><br><br>
  
=== dicefontd10 ===
+
<div style="column-count: 2;">
 +
==== dicefontd10 ====
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,534: Line 2,524:
 
|}
 
|}
  
=== dicefontd12 ===
+
<h4 style="margin-top: 0px;padding-top:0.85em;"> dicefontd12 </h4>
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,579: Line 2,569:
 
{{icon character|dicefontd12|L|font-size:200%}}
 
{{icon character|dicefontd12|L|font-size:200%}}
 
|}
 
|}
 +
</div><br><br>
  
=== dicefontd20 ===
+
<div style="column-count: 2;">
 +
==== dicefontd20 ====
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,650: Line 2,642:
 
|}
 
|}
  
=== dicefontd30 ===
+
<h4 style="margin-top: 0px;padding-top:0.85em;"> dicefontd30 </h4>
 
{| class="wikitable" style="text-align:center"
 
{| class="wikitable" style="text-align:center"
 
|-
 
|-
Line 1,738: Line 2,730:
 
||{{icon character|dicefontd30|f|font-size:200%}}
 
||{{icon character|dicefontd30|f|font-size:200%}}
 
|}
 
|}
 +
</div>
  
=== fontello ===
+
= See Also =
{| class="wikitable" style="text-align:center"
+
* [[Building Character Sheets]]
|-
+
** [[Designing Character Sheet Layout]] - tips on how to best design the broad strokes of a character sheet
! Character !! Icon
+
** [[Image use in character sheets]] - How to include images on your character sheets
|-
+
** [[Building_Character_Sheets#Best_Practices|Sheet Design Best Practices]]
{{icon character extended|fontello|&#xe800;|&amp;#xe800;}}
+
** [[Sheetworkers]] - relevant for many of the more complicated tricks on the page
|-
+
** [[Sheet Author Tips]] - misc. best practices and good ideas for those who often work with sheets
{{icon character extended|fontello|&#xe801;|&amp;#xe801;}}
+
** [[Custom Sheet Sandbox|Sheet Sandbox]] the better editor to use when you code your character sheets
|-
+
* {{repo|roll20-character-sheets Roll20 GitHub repository}}
{{icon character extended|fontello|&#xe802;|&amp;#xe802;}}
+
* '''[[:Category:Character Sheet Creation|List of all pages related to "Character Sheet Creation"]]'''
|-
+
** [[Andreas Guide to Sheet Development]]
{{icon character extended|fontello|&#xe803;|&amp;#xe803;}}
+
==Guides==
|-
+
* [https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML Introduction to HTML] - MDN web docs
{{icon character extended|fontello|&#xe804;|&amp;#xe804;}}
+
* [https://developer.mozilla.org/en-US/docs/Learn/CSS/First_steps Introduction to CSS] - MDN web docs
|-
+
* [https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps Introduction to JavaScript] - MDN web docs
{{icon character extended|fontello|&#xe805;|&amp;#xe805;}}
+
<br>
|-
+
{{icon character extended|fontello|&#xe806;|&amp;#xe806;}}
+
|-
+
{{icon character extended|fontello|&#xe807;|&amp;#xe807;}}
+
|-
+
{{icon character extended|fontello|&#xe808;|&amp;#xe808;}}
+
|-
+
{{icon character extended|fontello|&#xe809;|&amp;#xe809;}}
+
|-
+
{{icon character extended|fontello|&#xe80a;|&amp;#xe80a;}}
+
|-
+
{{icon character extended|fontello|&#xe80b;|&amp;#xe80b;}}
+
|-
+
{{icon character extended|fontello|&#xe80c;|&amp;#xe80c;}}
+
|-
+
{{icon character extended|fontello|&#xe80d;|&amp;#xe80d;}}
+
|-
+
{{icon character extended|fontello|&#xe80e;|&amp;#xe80e;}}
+
|-
+
{{icon character extended|fontello|&#xe80f;|&amp;#xe80f;}}
+
|-
+
{{icon character extended|fontello|&#xe810;|&amp;#xe810;}}
+
|-
+
{{icon character extended|fontello|&#xe811;|&amp;#xe811;}}
+
|-
+
{{icon character extended|fontello|&#xe812;|&amp;#xe812;}}
+
|-
+
{{icon character extended|fontello|&#xe813;|&amp;#xe813;}}
+
|-
+
{{icon character extended|fontello|&#xf008;|&amp;#xf008;}}
+
|}
+
 
+
== Styling Roll Buttons ==
+
Want a roll button that doesn't have a d20 image in it? Simple!
+
<pre data-language="css">button[type=roll].sheet-blank-roll-button::before { content: ''; }</pre>
+
The d20 is a single character with the [[#dicefontd20|dicefontd20]] font-family in the button's <code>::before</code> pseudo-element. Setting the content to an empty string removes it. If you want to put something other than a d20 in its place, you'll have to change the font-family as well.
+
 
+
== Replicating the JavaScript Math Library ==
+
{{note|With the advent of [[Sheet Worker Scripts]], the value of these tricks is diminished, as you can use the Math library directly with a sheet worker. However, they are kept here for legacy purposes, and because they still work.}}
+
 
+
It is possible to replicate most of the functionality of JavaScript's Math library with autocalc fields. While this isn't technically ''CSS'' Wizardry, it is included in this article for ease of discovery. Functions with an asterisk (*) produce approximate results; you can increase their accuracy by adding iterations to the computation.
+
 
+
Some functions below may reference other functions below.
+
 
+
=== Constants ===
+
The following are constants in JavaScript, so there's no reason to not have them as constants if you happen to need them.
+
 
+
==== E ====
+
E is Euler's constant, the base for the [[wikipedia:natural logarithm|natural logarithm]]. The exact value is <code>Sum[1 / n!, {n, 0, Infinity}]</code>. <code>Math.log(Math.E)</code> is 1.
+
<pre data-language="html"><input type="hidden" name="attr_cE" value="2.718281828459045"></pre>
+
 
+
==== LN2 ====
+
LN2 is the value of <code>Math.log(2)</code>.
+
<pre data-language="html"><input type="hidden" name="attr_cLN2" value="0.6931471805599453"></pre>
+
 
+
==== LN10 ====
+
LN10 is the value of <code>Math.log(10)</code>.
+
<pre data-language="html"><input type="hidden" name="attr_cLN10" value="2.302585092994046"></pre>
+
 
+
==== LOG2E ====
+
LOG2E is the base-2 logarithm of E. In other words, the value of <code>Math.log2(Math.E)</code>, or <code>1 / Math.log(2)</code>.
+
<pre data-language="html"><input type="hidden" name="attr_cLOG2E" value="1.4426950408889634"></pre>
+
 
+
==== LOG10E ====
+
LOG10E is the base-10 logarithm of E. In other words, the value of <code>Math.log10(Math.E)</code>, or <code>1 / Math.log(10)</code>.
+
<pre data-language="html"><input type="hidden" name="attr_cLOG10E" value="0.4342944819032518"></pre>
+
 
+
==== PI ====
+
PI is the radio between the circumference and diameter of a circle.
+
<pre data-language="html"><input type="hidden" name="attr_cPI" value="3.141592653589793"></pre>
+
 
+
==== SQRT1_2 ====
+
SQRT1_2 is the square root of 1/2 (0.5).
+
<pre data-language="html"><input type="hidden" name="attr_cSQRT1_2" value="0.7071067811865476"></pre>
+
 
+
==== SQRT2 ====
+
SQRT2 is the square root of 2.
+
<pre data-language="html"><input type="hidden" name="attr_cSQRT2" value="1.4142135623730951"></pre>
+
 
+
=== Trivially Represented ===
+
The following functions you can use in autocalc fields easily, either because they're simple or because they're directly available.
+
 
+
==== abs(x) ====
+
If <code>x < 0</code>, the result is <code>-1 * x</code>. Otherwise, the result is <code>x</code>.
+
<pre data-language="html"><input type="hidden" name="attr_abs" value="abs(@{x})" disabled></pre>
+
 
+
==== cbrt(x) ====
+
Cube root: <code>cbrt(x)^3 = x</code>.
+
<pre data-language="html"><input type="hidden" name="attr_cbrt" value="(@{x}**(1/3))" disabled></pre>
+
 
+
==== ceil(x) ====
+
Rounds towards positive infinity.
+
<pre data-language="html"><input type="hidden" name="attr_ceil" value="ceil(@{x})" disabled></pre>
+
 
+
==== exp(x) ====
+
E^x.
+
<pre data-language="html"><input type="hidden" name="attr_exp" value="(@{cE}**@{x})" disabled></pre>
+
 
+
==== floor(x) ====
+
Rounds towards negative infinity.
+
<pre data-language="html"><input type="hidden" name="attr_floor" value="floor(@{x})" disabled></pre>
+
 
+
==== hypot(x, y, z, ...) ====
+
Hypotenuse: <code>sqrt(x^2 + y^2 + z^2 + ...)</code>.
+
<pre data-language="html"><!-- include as many values as you need -->
+
<input type="hidden" name="attr_hypot" value="((@{x}**2 + @{y}**2 + @{z}**2)**0.5)" disabled></pre>
+
 
+
==== max(x, y, z, ...) ====
+
If <code>x > y</code>, the result is <code>x</code>, while if <code>x < y</code>, the result is <code>y</code>.
+
<pre data-language="html"><input type="hidden" name="attr_max_xy" value="(((@{x} + @{y}) + abs(@{x} - @{y})) / 2)" disabled>
+
<input type="hidden" name="attr_max_xyz" value="(((@{max_xy} + @{z}) + abs(@{max_xy} - @{z})) / 2)" disabled>
+
<input type="hidden" name="attr_max_xyzw" value="(((@{max_xyz} + @{w}) + abs(@{max_xyz} - @{w})) / 2)" disabled></pre>
+
 
+
==== min(x, y, z, ...) ====
+
If <code>x > y</code>, the result is <code>y</code>, while if <code>x < y</code>, the result is <code>x</code>.
+
<pre data-language="html"><input type="hidden" name="attr_min_xy" value="(((@{x} + @{y}) - abs(@{x} - @{y})) / 2)" disabled>
+
<input type="hidden" name="attr_min_xyz" value="(((@{min_xy} + @{z}) - abs(@{min_xy} - @{z})) / 2)" disabled>
+
<input type="hidden" name="attr_min_xyzw" value="(((@{min_xyz} + @{w}) - abs(@{min_xyz} - @{w})) / 2)" disabled></pre>
+
 
+
==== pow(x, y) ====
+
<code>x^y</code>, this is <code>x</code> multiplied by itself <code>y</code> times; fractional values for <code>y</code> are permissible.
+
<pre data-language="html"><input type="number" name="attr_pow" value="(@{x}**@{y})" disabled></pre>
+
 
+
==== round(x) ====
+
Round towards the nearest integer. If the fractional part is 0.5, it will round towards positive infinity.
+
<pre data-language="html"><input type="hidden" name="attr_round" value="round(@{x})" disabled></pre>
+
 
+
==== sign(x) ====
+
If <code>x < 0</code>, the result is -1, while if <code>x > 0</code>, the result is 1 and if <code>x = 0</code>, the result is 0. Unfortunately, because of the nature of the calculation, we cannot correctly calculate the final possibility. This works for all values of <code>x</code> other than 0, however.
+
<pre data-language="html"><input type="hidden" name="attr_sign" value="(@{x} / abs(@{x}))" disabled></pre>
+
 
+
==== sqrt(x) ====
+
Square root: <code>sqrt(x)^2 = x</code>.
+
<pre data-language="html"><input type="hidden" name="attr_sqrt" value="(@{x}**0.5)" disabled></pre>
+
 
+
==== trunc(x) ====
+
Round towards 0.
+
<pre data-language="html"><input type="hidden" name="attr_trunc" value="(@{sign} * floor(abs(@{x})))" disabled></pre>
+
 
+
=== Trigonometric Functions ===
+
Trigonometric functions deal with angles and the relations between the sides of a triangle.
+
 
+
==== *acos(x) ====
+
Inverse function of <code>cos</code>; <code>acos(cos(x)) = x</code>, and if <code>abs(x) <= 1</code>, <code>cos(acos(x)) = x</code>.
+
<pre data-language="html"><input type="hidden" name="attr_acos" value="(@{cPI} / 2 - @{asin})" disabled></pre>
+
 
+
==== *acosh(x) ====
+
Hyperbolic arccosine.
+
 
+
{{note|See [[#*log(x)|log(x)]] for the definition of <code>@{log_2x}</code>}}
+
<pre data-language="html"><input type="hidden" name="attr_acosh" value="(@{log_2x} - (1 / (4 * @{x}**2) + 3 / (32 * @{x}**4) + 15 / (288 * @{x}**6) + 105 / (384 * @{x}**8)))" disabled></pre>
+
 
+
==== *asin(x) ====
+
Inverse function of <code>sin</code>; <code>asin(sin(x))</code> = x, and if <code>abs(x) <= 1</code>, <code>sin(asin(x)) = x</code>.
+
<pre data-language="html"><input type="hidden" name="attr_asin" value="(@{x} + @{x}**3 / 6 + (3 * @{x}**5) / 40 + (15 * @{x}**7) / 336)" disabled></pre>
+
 
+
==== *asinh(x) ====
+
Hyperbolic arcsine.
+
<pre data-language="html"><input type="hidden" name="attr_asinh" value="(@{x} - @{x}**3 / 6 + 3 * @{x}**5 / 40 - 15 * @{x}**7 / 336)" disabled></pre>
+
 
+
==== *atan(x) ====
+
Inverse function of <code>tan</code>; <code>atan(tan(x)) = x = tan(atan(x))</code>.
+
<pre data-language="html"><input type="hidden" name="attr_atan" value="(@{x} - @{x}**3 / 3 + @{x}**5 / 5 - @{x}**7 / 7)" disabled></pre>
+
 
+
==== *atanh(x) ====
+
Hyperbolic arctangent.
+
<pre data-language="html"><input type="hidden" name="attr_atan" value="(@{x} + @{x}**3 / 3 + @{x}**5 / 5 + @{x}**7 / 7)" disabled></pre>
+
 
+
==== *atan2(y, x) ====
+
The same as <code>atan(y / x)</code>, although the actual Math library function gracefully handles <code>x = 0</code>.
+
<pre data-language="html"><input type="hidden" name="attr_atan2" value="((@{y} / @{x}) - (@{y} / @{x})**3 / 3 + (@{y} / @{x})**5 / 5 - (@{y} / @{x})**7 / 7)" disabled></pre>
+
 
+
==== *cos(x) ====
+
The ratio of the adjacent side of a triganle from the given angle (on a right triangle) to the hypotenuse.
+
<pre data-language="html"><input type="hidden" name="attr_cos" value="(1 - @{x}**2 / 2 + @{x}**4 / 24 - @{x}**6 / 720 + @{x}**8 / 40320)" disabled></pre>
+
 
+
==== *cosh(x) ====
+
Hyperbolic cosine.
+
<pre data-language="html"><input type="hidden" name="attr_cosh" value="(1 + @{x}**2 / 2 + @{x}**4 / 24 + @{x}**6 / 720 + @{x}**8 / 40320)" disabled></pre>
+
 
+
==== *sin(x) ====
+
The ratio of the opposite side of a triangle from the given angle (on a right triangle) to the hypotenuse.
+
<pre data-language="html"><input type="hidden" name="attr_sin" value="(@{x} - @{x}**3 / 6 + @{x}**5 / 120 - @{x}**7 / 5040)" disabled></pre>
+
 
+
==== *sinh(x) ====
+
Hyperbolic sine.
+
<pre data-language="html"><input type="hidden" name="attr_sinh" value="(@{x} + @{x}**3 / 6 + @{x}**5 / 120 + @{x}**7 / 5040)" disabled></pre>
+
 
+
==== *tan(x) ====
+
The ratio of the opposite side of a triangle from the given angle (on a right triangle) to the adjacent side.
+
<pre data-language="html"><input type="hidden" name="attr_tan" value="(@{sin} / @{cos})" disabled></pre>
+
 
+
==== *tanh(x) ====
+
Hyperbolic tangent.
+
<pre data-language="html"><input type="hidden" name="attr_tanh" value="(@{sinh} / @{cosh})" disabled></pre>
+
 
+
=== Other Transcendental Functions ===
+
Transcendental functions cannot be expressed with a finite number of algebraic operations. The [[#Trigonometric Functions|trigonometric functions]] are transcendental as well.
+
 
+
==== *log(x) ====
+
Natural logarithm (log base E) of x.
+
<pre data-language="html"><input type="hidden" name="attr_log" value="(@{x} - @{x}**2 / 2 + @{x}**3 / 3 - @{x}**4 / 4 + @{x}**5 / 5 - @{x}**6 / 6 + @{x}**7 / 7)" disabled>
+
<!-- used for acosh, above -->
+
<input type="hidden" name="attr_log_2x" value="(@{cLN2} + @{log})" disabled></pre>
+
 
+
==== *log1p(x) ====
+
<code>log(1 + x)</code>
+
<pre data-language="html"><input type="hidden" name="attr_log1p" value="((1 + @{x}) - (1 + @{x})**2 / 2 + (1 + @{x})**3 / 3 - (1 + @{x})**4 / 4 + (1 + @{x})**5 / 5 - (1 + @{x})**6 / 6 + (1 + @{x})**7 / 7)" disabled></pre>
+
 
+
==== *log10(x) ====
+
Log base 10 of x.
+
<pre data-language="html"><input type="hidden" name="attr_log10" value="(@{log} / @{cLN10})" disabled></pre>
+
 
+
==== *log2(x) ====
+
Log base 2 of x.
+
<pre data-language="html"><input type="hidden" name="attr_log2" value="(@{log} / @{cLN2})" disabled></pre>
+
 
+
=== Other Functions ===
+
The following functions don't really have a mathematical categorization.
+
 
+
==== *clz32(x) ====
+
The number of leading zero bits in a 32-bit number. For example, the number 64 is represented in binary as <code>00000000000000000000000001000000</code>, so <code>clz32(64) = 25</code>.
+
<pre data-language="html"><input type="hidden" name="attr_clz32" value="(32 - ceil(@{log1p} / @{cLN2}))" disabled></pre>
+
 
+
=== Impossible to Implement ===
+
Some functions cannot be implemented with autocalc fields.
+
 
+
==== fround(x) ====
+
Rounds a number to the nearest 32-bit representable floating point number.
+
 
+
==== imul(x, y) ====
+
Perform 32-bit multiplication. This is functionally equivalent to <code>trunc(x * y)</code>, except when large numbers get involved.
+
 
+
==== random() ====
+
Generate a random number in the range [0, 1). While this can't be done with autocalc fields, you ''can'' roll dice!
+
 
+
<br />
+
 
[[Category:Character Sheet Creation]]
 
[[Category:Character Sheet Creation]]
[[Category:Guides]]
 

Latest revision as of 19:43, 20 August 2023

Learning CSS and HTML can be daunting. And yet, if you want to create a character sheet for Roll20, both are required.

This page gives a number of examples for creative way to use CSS within the limits of the Roll20 Character Sheet Framework.

This article and some of the tips originate from the old CSS Wizardry-thread in the Character Sheets forum, but have over the years expanded beyond that as the framework matured with changes like CSE and newer CSS features like Flexbox or CSS Grid Some tips don't necessarily involve CSS at all, but are quirks in the sheet system.


Many of these tips also include links to a live demo on JSFiddle, so you can see them in action.


CSS Specificity

Other pages related to CSS & Design for Roll20 character sheets:


Contents

[edit] Elements

Tips for styling many of the basic elements of a sheet

[edit] Number/Text Input

[edit] textarea

by Alan S.

For some reason, the default for the input elements is box-sizing: border-box, while the default for the textarea was box-sizing: content-box. This leads the default textarea to more easily take more space than it should, overflowing it parent element, while inputs more rarely do. See more: box-sizing

Explicitly adding a box-sizing to my textarea css, seen below, fixed the problem.

.charsheet textarea.textbox {
    resize: none;
    box-sizing: border-box;
    height: 231px;
}

[edit] Content-scaled Inputs

By stacking your input with an attribute-backed span that defines the dimensions of the parent, you can create inputs that resize themselves to fit their text.

The html baseline is a simple parent container with your input and span inside:

<div class="autoExpand">
 <input type="text" name="attr_expandText">
 <span name="attr_expandText"></span>
</div>

Here is the CSS with comments about what the properties are doing for you:

.autoExpand {
    overflow: hidden; /* hides overflow that is caused by the span */
    position: relative; /* Allows the input's absolute positioning to be relative to this parent div */
    min-width: 50px; /* Whatever feels good to you, prevents a new span from collapsing to 0. */
    width: fit-content; /* Will fit the width to the contents without going outside itself or collapsing smaller than its content, though we will be using min-width in the case where there's no content. */
    height: 20px; /* Prevent the span from defining the height of the input container which can't be multiline */
}
.autoExpand span {
    visibility: hidden;
    font-size: 1em;
    padding: 0 8px; /* Matching my input's padding so that the widths are correct */
}
.autoExpand input {
    position: absolute; /* removes the input from the DOM flow, allowing the span to exist in the same space */
    width: 100%; /* Inputs match the width of the parent element as defined by the spans */
    font-size: 1em;
}

[edit] Four Ways to Use an Attribute

[edit] Standard

Create one of: <input> (with a type attribute of "text", "number", "checkbox", "radio", or "hidden"), <select>, or <textarea>, and set the element's name attribute to a value beginning with "attr_"

<input type="text" name="attr_text_example">
<input type="number" name="attr_number_example">
<input type="checkbox" name="attr_checkbox_example">
<input type="radio" name="attr_radio_example">
<input type="hidden" name="attr_hidden_example" value="0">
<select name="attr_select_example">
    <option value="1">First option</option>
    <option value="2">Second option</option>
</select>
<textarea name="attr_textarea_example"></textarea>

The value of the form element will be stored as the value of an attribute with the same name as the form element, except the "attr_" prefix will be removed. So, an element named "attr_example" will be stored in the attribute "example".

Text inputs, number inputs, and textareas will not update the backing attribute until they lose focus, for example when you click elsewhere on the sheet or hit the [tab] key.

Hidden inputs are, as you might guess, hidden to the user. They cannot be interacted with, and so they are prime candidates for intermediate calculations of autocalc or storing things the user doesn't need to see or change for Sheet Worker Scripts.

If you have multiple radio inputs with the same name, only one of those radios will be checked at any given time. If you have multiple other kinds of elements with the same name, their values will be synchronized. This can be used, for example, if you have a tab layout with the same field present in two tabs. Give them both the same name, and they will always have the same value.

[edit] Autocalc Fields

Main Page: Auto-Calc

If a field has the disabled attribute, the user will be unable to modify its value and its value will be treated as a mathematical equation (which can reference other attributes of the character). The result of that formula will be what the user sees. Errors in the formula (for example, @{a} + @{b} + @{c} when attribute b has no value) will result in no output.

When using sheet workers, the value of the autocalc field you get from the getAttrs function will be its formula, and you cannot set its value to something else. See sheetworker-autocalc for a utility to resolve autocalc fields to their calculated value in a sheet worker script. Note: sheetworker-autocalc has not been tested with repeating fields.

[edit] Readonly Fields

If a field has the readonly attribute, the user will be unable to modify its value, and its default styling will be the same as if it were disabled. However, sheet worker scripts will be able to modify its value, and if its value is some kind of equation, it won't be automatically calculated. Note: If for some reason a readonly field is an equation, sheetworker-autocalc will be able to resolve it to a value just fine.

[edit] Attribute-Backed <span>s

A <span> element can be given an attr_ name, just like one of the form elements, above. This will cause the span to behave similarly to a readonly field in that the user cannot modify it directly, and sheet worker scripts have no trouble doing so. There are two main differences with an attribute backed span:

  1. The default styling: the span will look just like the surrounding text
  2. When using attribute backed spans in a repeating section, you should always make a hidden input version of the attribute as attribute backed spans in repeating sections cannot be called without the full repeating section syntax (e.g. @{repeating_SECTIONNAME_$X_attribute_name}).

[edit] Checkbox and Radio Input

Checkboxes and radio buttons don't like getting changed much. Instead, it can be easier to use a hidden attribute and present a button to update the attribute.

New Pure HTML + CSS styling with Labels

This is Richard T's updated pure HTML + CSS that uses labels to force interior elements to be part of the input.

https://jsfiddle.net/medieve/rfkvu3hm/

Old Checkbox / Radio example (pure html+css, but less intuitive and difficult to align)

Note: This example/method of creating styled checkboxes or radios on a character sheet does not work out of the box, contains lots of unnecessary code, and is far less intuitive than the new button example. This example is generally not recommended to use, but is left here just for the sake of continuity, and those who might need troubleshoot their implementation. Live Demo

For a pure html+css approach with no sheet worker scripts, you can make the checkbox/radio invisible (but still clickable!) and overlay it on top of a more cooperative element.

<input type="checkbox"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
/* Hide actual radio/checkbox */
.charsheet input[type="radio"],
.charsheet input[type="checkbox"] {
    opacity: 0;
    width: 16px;
    height: 16px;
    position: relative;
    top: 5px;
    left: 6px;
    margin: -10px;
    cursor: pointer;
    z-index: 1;
}

/* Fake radio/checkbox */
.charsheet input[type="radio"] + span::before,
.charsheet input[type="checkbox"] + span::before {
    margin-right: 4px;
    border: solid 1px #a8a8a8;
    line-height: 14px;
    text-align: center;
    display: inline-block;
    vertical-align: middle;
    box-shadow: 0 0 2px #ccc;
    background: #f6f6f6;
    background: radial-gradient(#f6f6f6, #dfdfdf);
}

/* Fake radio */
.charsheet input[type="radio"] + span::before {
    content: "";
    width: 12px;
    height: 12px;
    font-size: 24px;
    border-radius: 50%;
}

.charsheet input[type="radio"]:checked + span::before {
    content: "•";
}

/* Fake checkbox */
.charsheet input[type="checkbox"] + span::before {
    content: "";
    width: 14px;
    height: 14px;
    font-size: 12px;
    border-radius: 3px;
}

.charsheet input[type="checkbox"]:checked + span::before {
    content: "✓";
}

The key here is the opacity: 0; set on the actual input, and then the width, height, and content set on the span::before immediately following the input. The checkbox/radio will be on top of the span: invisible, but still clickable. When the checkbox/radio is selected, then, the style on the span::before is changed.

Note: Because of the way this is set up, if you do not have a span element immediately following your checkbox/radio button, the checkbox/radio button will not be visible.

(The following examples use CSS flex for alignment. For more on flex layouts, see Building Character Sheets#Layout)

[edit] Checkbox

<div class="sheet-toggle-container">
  <input type="hidden" class="sheet-toggle" name="attr_show_flag" />
  <button type="action" name="act_show" class="sheet-toggle">
    <span class="sheet-checked">✓</span>
  </button>
  <span>Show?</span>
</div>
<script type="text/worker">
  // Register the click handler to all specified buttons.
  const toggleList = ["show"];
  toggleList.forEach(function(button) {
    on(`clicked:${button}`, function() {
      const flag = `${button}_flag`;
      // Check the current value of the hidden flag.
      getAttrs([flag], function(v) {
        // Update the value of the hidden flag to "1" for checked or "0" for unchecked.
        setAttrs({
          [flag]: v[flag] !== "1" ? "1" : "0"
        });
      });
    });
  });
</script>
/* Configure a container for the toggle */
.charsheet .sheet-toggle-container {
  display: inline-flex;
  align-items: center;
}

/* Configure the button styling. This example makes it look like a checkbox. */
.charsheet button.sheet-toggle {
  padding: 0;
  border: solid 1px #a8a8a8;
  cursor: pointer;

  width: 16px;
  height: 16px;
  border-radius: 3px;
  display: flex;
  justify-content: center;
  align-items: center;

  font-size: 12px;
}

/* Hide the "checked" section of the toggle if the attribute value is not "1". */
.charsheet input.sheet-toggle:not([value="1"]) ~ button.sheet-toggle > span.sheet-checked {
  display: none;
}

[edit] Radio

<div class="sheet-radios">
  <input type="hidden" class="sheet-radio" name="attr_level" value="1" />
  <button type="action" name="act_level_1" class="sheet-radio sheet-radio-1">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_level_2" class="sheet-radio sheet-radio-2">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_level_3" class="sheet-radio sheet-radio-3">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_level_4" class="sheet-radio sheet-radio-4">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_level_5" class="sheet-radio sheet-radio-5">
    <span class="sheet-checked"></span>
  </button>
</div>
<script type="text/worker">
  const levelRadioValues = ["1","2","3","4","5"];
  levelRadioValues.forEach(function(value) {
    on(`clicked:level_${value}`, function() {
      setAttrs({
        ["level"]: value
      });
    });
  });
</script>
/* Configure a container for the radio buttons. */
.charsheet .sheet-radios {
  display: flex;
  align-items: center;
}

/* Configure the button styling. This example makes it look like a radio. */
.charsheet button.sheet-radio {
  padding: 0;
  border: solid 1px #a8a8a8;
  cursor: pointer;

  width: 14px;
  height: 14px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.charsheet button.sheet-radio > span.sheet-checked {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: buttontext;
}

/* Hide the "checked" section of the radio if the attribute value does not match the radio */
.charsheet input.sheet-radio:not([value="1"]) ~ button.sheet-radio-1 > span.sheet-checked,
.charsheet input.sheet-radio:not([value="2"]) ~ button.sheet-radio-2 > span.sheet-checked,
.charsheet input.sheet-radio:not([value="3"]) ~ button.sheet-radio-3 > span.sheet-checked,
.charsheet input.sheet-radio:not([value="4"]) ~ button.sheet-radio-4 > span.sheet-checked,
.charsheet input.sheet-radio:not([value="5"]) ~ button.sheet-radio-5 > span.sheet-checked {
  display: none;
}

[edit] Checkbox Alternative

You're not restricted to a box with a check on it if you want a binary state (on or off). When styling your checkbox (or radio button!) you can use just about anything.

Old example (pure html+css)

Live Demo

/* Fake checkbox */
input[type="checkbox"] + span::before {
    margin-right: 4px;
    line-height: 14px;
    text-align: center;
    display: inline-block;
    vertical-align: middle;
    content: "▼";
    width: 14px;
    height: 14px;
    font-size: 12px;
}

input[type="checkbox"]:checked + span::before {
    content: "►";
}

You can also use an image instead of a string, such as content: url(http://i.imgur.com/90HuQPr.png);

<input type="hidden" class="direction" name="attr_direction" value="down" />
<button type="action" name="act_direction_toggle" class="direction">
  <span class="down">▼</span>
  <span class="right">►</span>
</button>
<script type="text/worker">
  on("clicked:direction_toggle", function() {
    // Check the current value of the hidden attribute.
    getAttrs(["direction"], function(v) {
      // Toggle the hidden attribute value between "down" and "right"
      setAttrs({
        "direction": v["direction"] !== "down" ? "down" : "right"
      });
    });
  });
</script>
/* Clear default button styling */
button.sheet-direction {
  margin: 0;
  overflow: visible;
  text-transform: none;
  border-style: none;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

/* Hide the section(s) that do not match the attribute value */
input.sheet-direction:not([value="down"]) + button.sheet-direction > span.sheet-down {
  display: none;
}
input.sheet-direction:not([value="right"]) + button.sheet-direction > span.sheet-right {
  display: none;
}

Now, instead of an empty box, or a box with a checkmark, you've got a right-pointing arrow or a down-pointing arrow. You can hide/display any other html content based on the hidden attribute value.

[edit] Fill Radio Buttons to the Left

A number of games use a set of bubbles, filled in from left to right, to represent various traits. For example, selecting the third bubble in "Strength" to indicate a Strength value of 3 should also fill in bubbles 1 and 2.

Example by Dana - jsfiddle

<div class="radiocontainer">
  <input checked="checked" class="radiobox reset" type="radio" name="attr_strength" value="0">
  <input class="radiobox" type="radio" name="attr_strength" value="1">
  <input class="radiobox" type="radio" name="attr_strength" value="2">
  <input class="radiobox" type="radio" name="attr_strength" value="3">
  <input class="radiobox" type="radio" name="attr_strength" value="4">
  <input class="radiobox" type="radio" name="attr_strength" value="5">
  <input class="radiobox" type="radio" name="attr_strength" value="6">
</div>
.radiocontainer {
  display: flex;
  margin: 2rem;
  gap: 10px;
}

.radiobox {
  order: 1;
  border: 1px solid black;
  border-radius: 50%;
  background: grey;
  height: 2rem;
  width: 2rem;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  z-index: 1;
}

.radiobox:checked {
  opacity: 0;
  z-index: 0;
  position: absolute;
}

.radiobox:checked ~ .radiobox {
  order: 3;
  background-color: transparent;
}

.radiobox.reset {
  order: 2;
  background-color: black;
}

Note:

  • When using this trick inside a Repeating Section, it will not work if there are underscores in an action button name. The above buttons should be named attr_strength1 or attr_strength-1 - they cant be attr_strength_1.
  • All radio buttons which are siblings will be affected by the selection of one of the radios. It is therefore recommended that you wrap the button group in some element, such as span or div.
Filled-buttons.gif

Alternative(older) implementations:

HTML/CSS+ sheetworker implementation
<div class="sheet-dots">
  <input type="hidden" name="attr_strength" class="sheet-dot" value="1" />
  <button type="action" name="act_strength_1" class="dot">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_strength_2" class="sheet-dot sheet-gt-1">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_strength_3" class="sheet-dot sheet-gt-1 sheet-gt-2">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_strength_4" class="sheet-dot sheet-gt-1 sheet-gt-2 sheet-gt-3">
    <span class="sheet-checked"></span>
  </button>
  <button type="action" name="act_strength_5" class="sheet-dot sheet-gt-1 sheet-gt-2 sheet-gt-3 sheet-gt-4">
    <span class="sheet-checked"></span>
  </button>
</div>
<script type="text/worker">
  const strengthValues = ["1","2","3","4","5"];
  strengthValues.forEach(function(value) {
    on(`clicked:strength_${value}`, function() {
      setAttrs({
        "strength": value
      });
    });
  });
</script>
.charsheet .sheet-dots{
  display:flex;
}
/* Configure the button styling. This example makes it look like a radio. */
.charsheet button.sheet-dot {
  padding: 0;
  border: solid 1px #a8a8a8;
  cursor: pointer;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.charsheet button.sheet-dot > span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: buttontext;
}

/* Hide the "checked" section of the radio if the hidden attribute value is greater than the button value */
.charsheet input.sheet-dot[value="1"] ~ button.sheet-gt-1 > span.sheet-checked {
  display: none;
}
.charsheet input.sheet-dot[value="2"] ~ button.sheet-gt-2 > span.sheet-checked {
  display: none;
}
.charsheet input.sheet-dot[value="3"] ~ button.sheet-gt-3 > span.sheet-checked {
  display: none;
}
.charsheet input.sheet-dot[value="4"] ~ button.sheet-gt-4 > span.sheet-checked {
  display: none;
}

Here, the gt-* classes are used to indicate "greater than" a particular value. This doesn't use any kind of math, so all distinct gt-* classes have to be included. For example, dot 3 has classes gt-1 and gt-2 because 3 is greater than both 1 and 2. Dot 1 does not have any gt-* classes because it is not greater than any of the other options. (If a zero option is possible, then all of these buttons will need a gt-0 class.)

Each possible value of the attribute needs a corresponding CSS rule to hide all values greater than that value. (The value of "5" doesn't need a rule here because there are no options greater than 5.)

This example uses a subtractive approach, meaning a that by default the button will indicate it is checked unless a CSS rule hides the "checked" span.

Old html+css implementation

Live Demo

Radio buttons can only have one selected value, however, and if we used a set of checkboxes, it would be annoying to make the user click each and every one of them to set the character's attribute. Also, a set of checkboxes would make macros extremely ugly: @{Strength_1} * 1 + @{Strength_2} * 1 + ...

However, with the radio button styling, we can solve this problem and use a radio button anyway, and only have one value.

<input type="radio" name="attr_r" value="1" checked="checked"><span></span>
<input type="radio" name="attr_r" value="2"><span></span>
<input type="radio" name="attr_r" value="3"><span></span>
<input type="radio" name="attr_r" value="4"><span></span>
<input type="radio" name="attr_r" value="5"><span></span>
/* Hide actual radio */
.charsheet input[type="radio"] {
    opacity: 0;
    width: 16px;
    height: 16px;
    position: relative;
    top: 5px;
    left: 6px;
    margin: -10px;
    cursor: pointer;
    z-index: 1;
}

/* Fake radio */
.charsheet input[type="radio"] + span::before {
    margin-right: 4px;
    border: solid 1px #a8a8a8;
    line-height: 14px;
    text-align: center;
    display: inline-block;
    vertical-align: middle;
    box-shadow: 0 0 2px #ccc;
    background: #f6f6f6;
    background: radial-gradient(#f6f6f6, #dfdfdf);
    content: "•";
    width: 12px;
    height: 12px;
    font-size: 24px;
    border-radius: 50%;
}

/* Remove dot from all radios _after_ selected one */
.charsheet input[type="radio"]:checked ~ input[type="radio"] + span::before {    
    content: "";
}

Here, all radio buttons are styled by default to appear as though they're checked. The radio buttons after the one that's actually checked then have the dot removed. The result is that the checked radio button and all of the ones to the left are "filled in," while the ones to the left are empty. You can invert this behavior (right of the checked radio are filled, checked and left of checked are empty) by swapping the two content lines.

To reverse this behavior (checked radio and right of checked radio are filled, left of checked radio are empty), swap the two content lines and change the last selector to this:

input[type="radio"]:checked ~ input[type="radio"] + span::before,
input[type="radio"]:checked + span::before

Note: If no radio button is selected, all of them will appear filled in (or all will appear empty if you've reversed/inverted the CSS). Therefore, it is wise to include checked="checked" on one of the radio buttons. That said, you may desire a "zero" value for the trait in question. I recommend having an extra radio button with value="0" checked="checked" and without the span element immediately following it. This will give you an initial value of 0, your radio button group will appear as intended, and the "zero" value will not show up to the user.

[edit] Circular Layouts

Live Demo

Some character sheets have rather interesting layouts. Mage: the Ascension, for example, has a pair of traits called Quintessence and Paradox that are both mapped onto a wheel of checkboxes.

<div>
    <div>
        <input type="checkbox" class="sheet-wheel sheet-wheel9 sheet-middle sheet-left-1" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel27 sheet-mid-three-eighth sheet-left-2" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel45 sheet-mid-quarter sheet-left-3" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel63 sheet-mid-eighth sheet-left-4" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel81 sheet-top sheet-left-5" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel99 sheet-top sheet-left-6" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel117 sheet-mid-eighth sheet-left-7" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel135 sheet-mid-quarter sheet-left-8" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel153 sheet-mid-three-eighth sheet-left-9" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel171 sheet-middle sheet-left-10" value="1"><span></span>
    </div>
    <div>
        <input type="checkbox" class="sheet-wheel sheet-wheel171 sheet-middle-2 sheet-left-1" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel153 sheet-mid-five-eighth sheet-left-2" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel135 sheet-mid-three-quarter sheet-left-3" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel117 sheet-mid-seven-eighth sheet-left-4" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel99 sheet-bottom sheet-left-5" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel81 sheet-bottom sheet-left-6" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel63 sheet-mid-seven-eighth sheet-left-7" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel45 sheet-mid-three-quarter sheet-left-8" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel27 sheet-mid-five-eighth sheet-left-9" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel9 sheet-middle-2 sheet-left-10" value="1"><span></span>
    </div>
    <div class="sheet-marker">•</div>
</div>
/* Hide actual checkbox */
.charsheet input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 15px;
    height: 15px;
    cursor: pointer;
    z-index: 1;
    margin-top: 6px;
}

/* Fake checkbox */
.charsheet input[type="checkbox"] + span::before {
    border: solid 1px #a8a8a8;
    line-height: 14px;
    text-align: middle;
    display: inline-block;
    vertical-align: middle;
    box-shadow: 0 0 2px #ccc;
    background: #f6f6f6;
    background: radial-gradient(#f6f6f6, #dfdfdf);
    position: relative;
    content: "";
    width: 14px;
    height: 14px;
    font-size: 12px;
    border-radius: 3px;
}

/* Styles unique to fake checkbox (checked) */
.charsheet input[type="checkbox"]:checked + span::before {
    content: "✓";
    color: #a00;
    box-shadow: 0 0 2px transparent;
}

/* Position checkboxes vertically in circle */
.charsheet input.sheet-top  { overflow:auto;white-space:pre-wrap;; }
.charsheet input.sheet-top + span::before { top: 0px; }
.charsheet input.sheet-mid-eighth { margin-top: 12px; }
.charsheet input.sheet-mid-eighth + span::before  { top: 7px; }
.charsheet input.sheet-mid-quarter { margin-top: 27px; }
.charsheet input.sheet-mid-quarter + span::before  { top: 22px; }
.charsheet input.sheet-mid-three-eighth { margin-top: 45px; }
.charsheet input.sheet-mid-three-eighth + span::before  { top: 40px; }
.charsheet input.sheet-middle { margin-top: 67px; }
.charsheet input.sheet-middle + span::before  { top: 62px; }
.charsheet input.sheet-middle-2 { margin-top: 73px; }
.charsheet input.sheet-middle-2 + span::before  { top: 68px; }
.charsheet input.sheet-mid-five-eighth { margin-top: 95px; }
.charsheet input.sheet-mid-five-eighth + span::before  { top: 90px; }
.charsheet input.sheet-mid-three-quarter { margin-top: 113px; }
.charsheet input.sheet-mid-three-quarter + span::before  { top: 108px; }
.charsheet input.sheet-mid-seven-eighth { margin-top: 127px; }
.charsheet input.sheet-mid-seven-eighth + span::before  { top: 122px; }
.charsheet input.sheet-bottom { margin-top: 135px; }
.charsheet input.sheet-bottom + span,
.charsheet input.sheet-bottom + span::before  { top: 130px; }

/* Position checkboxes horizontally in circle */
.charsheet input.sheet-left-1 { margin-left: 14px; }
.charsheet input.sheet-left-1 + span::before  { left: 14px; }
.charsheet input.sheet-left-2 { margin-left: 1px; }
.charsheet input.sheet-left-2 + span::before  { left: 1px; }
.charsheet input.sheet-left-3 { margin-left: -4px; }
.charsheet input.sheet-left-3 + span::before  { left: -4px; }
.charsheet input.sheet-left-4 { margin-left: -5px; }
.charsheet input.sheet-left-4 + span::before  { left: -5px; }
.charsheet input.sheet-left-5 { margin-left: -2px; }
.charsheet input.sheet-left-5 + span::before  { left: -2px; }
.charsheet input.sheet-left-6 { margin-left: 1px; }
.charsheet input.sheet-left-6 + span::before  { left: 1px; }
.charsheet input.sheet-left-7 { margin-left: 3px; }
.charsheet input.sheet-left-7 + span::before  { left: 3px; }
.charsheet input.sheet-left-8 { margin-left: 2px; }
.charsheet input.sheet-left-8 + span::before  { left: 2px; }
.charsheet input.sheet-left-9 { margin-left: -4px; }
.charsheet input.sheet-left-9 + span::before  { left: -4px; }
.charsheet input.sheet-left-10 { margin-left: -16px; }
.charsheet input.sheet-left-10 + span::before  { left: -16px; }

/* Rotate checkboxes */
.charsheet input.sheet-wheel9,
.charsheet input.sheet-wheel9 + span::before { transform: rotate(9deg); }
.charsheet input.sheet-wheel27,
.charsheet input.sheet-wheel27 + span::before { transform: rotate(27deg); }
.charsheet input.sheet-wheel45,
.charsheet input.sheet-wheel45 + span::before { transform: rotate(45deg); }
.charsheet input.sheet-wheel63,
.charsheet input.sheet-wheel63 + span::before { transform: rotate(63deg); }
.charsheet input.sheet-wheel81,
.charsheet input.sheet-wheel81 + span::before { transform: rotate(81deg); }
.charsheet input.sheet-wheel99,
.charsheet input.sheet-wheel99 + span::before { transform: rotate(99deg); }
.charsheet input.sheet-wheel117,
.charsheet input.sheet-wheel117 + span::before { transform: rotate(117deg); }
.charsheet input.sheet-wheel135,
.charsheet input.sheet-wheel135 + span::before { transform: rotate(135deg); }
.charsheet input.sheet-wheel153,
.charsheet input.sheet-wheel153 + span::before { transform: rotate(153deg); }
.charsheet input.sheet-wheel171,
.charsheet input.sheet-wheel171 + span::before { transform: rotate(171deg); }

.charsheet div.sheet-marker {
    margin: 36px 0px 0px 5px;
    font-size: 20px;
}

[edit] Select Dropdown

Live Demo

<select> elements are notoriously difficult to apply most styles to. However, using :hover pseudo-selectors and radio buttons, you can create something approximating a dropdown with whatever style you like.

<div class="sheet-container">
    <div class="sheet-child">
        <input type="radio" name="attr_radio" class="sheet-select-radio sheet-d4" value="1" checked="true" />
        <label>d4</label>
        <input type="radio" name="attr_radio" class="sheet-select-radio sheet-d8" value="2" />
        <label>d8</label>
        <div class="sheet-d4"></div>
        <div class="sheet-d8"></div>
    </div>
</div>
.charsheet .sheet-container {
    width: 280px;
}

.charsheet .sheet-container,
.charsheet .sheet-child {
    display: inline-block;
}

.charsheet .sheet-child {
    vertical-align: middle;
    width: 35px;
    height: 35px;
}

.charsheet .sheet-child input,
.charsheet .sheet-child input + label {
    display: none;
    z-index: 1;
}

.charsheet .sheet-child:hover {
    background: gray;
    position:absolute;
    width: 100px;
    height: auto;
    z-index: 1;
    padding: 5px;
}

.charsheet .sheet-child:hover > div.sheet-d4 {
    display: none;
}

.charsheet .sheet-child:hover input,
.charsheet .sheet-child:hover input + label {
    display: inline;
}

.charsheet .sheet-child:hover input + label {
   margin-right: 50%
}

.charsheet .sheet-child:hover label {
    display: inline-block;
}

.charsheet div.sheet-d4 {
    background-position: -411px -1px;
    width: 35px;
    height: 35px;
    background-image: url("https://i.imgur.com/zkgyBOi.png");
    background-repeat: no-repeat;
    color: transparent;
    display: none;
}

.charsheet div.sheet-d8 {
    background-position: -703px -1px;
    width: 35px;
    height: 35px;
    background-image: url("https://i.imgur.com/zkgyBOi.png");
    background-repeat: no-repeat;
    color: transparent;
    display: none;
}

.charsheet .sheet-child:not(:hover) input.sheet-select-radio.sheet-d4:checked ~ div.sheet-d4,
.charsheet .sheet-child:not(:hover) input.sheet-select-radio.sheet-d8:checked ~ div.sheet-d8 {
    display: block;
}

[edit] Select Dropdown Text

Here's another method to edit text within a select. Use an input with type="hidden" given the same attribute name as the select to trigger css styles on the select's option text.

<input name="attr_favcolor" type="hidden" class="sheet-color-switch">
<select name="attr_favcolor" class="sheet-color-select1">
  <option style="color:red;" value="0" selected>Red</option>
  <option style="color:yellow;" value="1">Yellow</option>
  <option style="color:blue;" value="2">Blue</option>
</select>
.charsheet .sheet-color-switch[value="0"] + .sheet-color-select1 {color: red;}
.charsheet .sheet-color-switch[value="1"] + .sheet-color-select1 {color: yellow;}
.charsheet .sheet-color-switch[value="2"] + .sheet-color-select1 {color: blue;}

[edit] Optgroup

How it looks on chrome. Firefox ignores al/most of styling.

If optgroup doesn't work, one workaround is to add a styled, disabled <option> in place of <optgroup>-element to work in a similar way.

select/option can be hard to style, and sometimes browsers don't show the styling. In the example, &nbsp; are used to indent the names, as padding & margin doesn't seem to work.

<select name="attr_ancestry">
  <option disabled style="background:gray;font-weight:bold;color:white;">&nbsp;&nbsp;&nbsp;&nbsp; MEN</option>
  <option value="Imperal">Imperal</option>
  <option value="Breton">Breton</option>
  <option value="Redguard">Redguard</option>
  <option value="Nord">Nord</option>
  <option disabled style="background:gray;font-weight:bold;color:white;">&nbsp;&nbsp;&nbsp;&nbsp; MER</option>
  <option value="Altmer">Altmer</option>
  <option value="Bosmer">Bosmer</option>
  <option value="Dunmer">Dunmer</option>
  <option value="Orsmer">Orsmer</option>
</select>

[edit] Button

See the Styling Roll Buttons on the Button-page for more examples. The examples can be adjusted to work with Action and Compendium buttons.

How to remove the default d20 from roll buttons:

CSS:

.charsheet button[type=roll].sheet-blank-button::before {
  content: '';
}

HTML:

<button class="sheet-blank-button" name="roll_BluffCheck" value="/roll 1d20 + @{Bluff}" type="roll">Bluff</button>

The d20 is a single character with the dicefontd20 font-family in the button's ::before pseudo-element. Setting the content to an empty string removes it.

[edit] Repeating Sections

main article: Repeating Sections - Styling

It's possible to style your Repeating Sections in a variety of ways, but it's a bit trickier than elsewhere on the sheet. You can't just write your CSS as though the <fieldset> that's in your HTML source is what the user is viewing, as roll20 creates their own elements and css for things that are inside.

You can do numerous things to alter how your repeating sections are displayed on the final character sheet. For example, you can have multiple repeating items per row:

.charsheet .repcontainer[data-groupname="repeating_skills"] > .repitem {
    display: inline-block;
}

Note: You do not prefix the rep* classes with sheet-!

Remember to use the [data-groupname="repeating_..."] attribute selector, if you want to only apply the style to a single repeating section. Of course, if you want the style to affect all of your repeating sections, that's not needed.

What Can't You Do?

You cannot:

  • Change the display property of the original <fieldset>.
  • Change the text of the Add, Modify/Done, Delete, or Move buttons.
    • However, you could set their opacity to 0 and display something in their place, much like styling checkboxes and radios, as well as add ::before or ::after pseudo-elements to them.
  • Change the display property of the Add button after the user has pressed Modify once.


How they are rendered on the sheet

After writing the code for your repeating section on your html file, here is how it will look when rendered to the user:

<fieldset class="repeating_my-repeating-section" style="display: none;">
    <!-- how the repating section code is structured in the browser after roll20 have created the parts inside it -->
</fieldset>
<div class="repcontainer" data-groupname="repeating_my-repeating-section">
    <div class="repitem">
        <div class="itemcontrol">
            <button class="btn btn-danger pictos repcontrol_del">#</button>
            <a class="btn repcontrol_move">≡</a>
        </div>
        <!-- my-repeating-section HTML -->
    </div>
    <!-- there will be a div.repitem for each item the user has actually added to the sheet -->
</div>
<div class="repcontrol" data-groupname="repeating_my-repeating-section">
    <button class="btn repcontrol_edit">Modify</button>
    <button class="btn repcontrol_add">+Add</button>
</div>

When you click the Modify button, the Add button is is set to display: none; and the text of the Modify button is changed to "Done". When you click Done, the Add button is set to display: inline-block; and the text of the Done button is changed to "Modify". While modifying repitems, the repcontainer gains the class "editmode".

[edit] Roll Templates

Apart from styling your own rolltemplates, you can also adjust the style of the Default Roll Template.

See Styling Roll Templates

[edit] Fonts

Roll20 supports five fonts (Arial, Patrick Hand, Contrail One, Shadows Into Light, and Candal) by default.

All the Websafe Fonts should also be available.

.charsheet span{
// defining more than one fint will only use the first one, but will fall back to the next font if the first one doesn't work for some reason.
   font-family: "Arial", "Patrick Hand";
}

See Also #Icon Fonts for the built-in special font on Roll20.

[edit] Google Fonts


Roll20 Character sheets supports importing fonts from Google Fonts.

For now, this is limited to Google Fonts: fonts.googleapis.com

Video Guide to use Google Fonts by Stephanie


New/Normal Sheets

When editing (default)CSE-sheets, you can use the @import styles generated by google as-is, including weights and variations; at least for the sheet's styling itself. However, since the roll templates still use the old style parser, you will need to use the legacy form below in order to use the font in your roll template.

Mobile

Roll20's mobile app currently has its own quirks for font use: BCS/Mobile#Custom_Fonts.


[edit] Legacy Sheet

The following instruction applies if you are working with a Legacy Sheet.

Legacy Sheets strictly uses the CSS-1 rule for importing fonts which is different from the auto-generated CSS-2 @import rule, which Google now uses (as of April 2020).

Examples of a Roll20 formatted import rules:

For the font "Sigmar One":

@import url('https://fonts.googleapis.com/css?family=Sigmar+One&display=swap');

or multiple font-families into Roll20, importing "Zilla Slab" and "Anton":

@import url('https://fonts.googleapis.com/css?family=Zilla+Slab|Anton&display=swap');

Example of an autogenerated @import rule by Google Fonts

for "Anton" and "Roboto" (
that doesn't work out-of-the-box
)
:
@import url('https://fonts.googleapis.com/css2?family=Anton&family=Roboto&display=swap');

Things that requires changes:

1. Edit the css2 to css when importing fonts.

2. When bringing in multiple fonts, you will need to replace & with |, and you do not need to repeat the family=


3. Some fonts may require a font weight. ie Open+Sans+Condensed:wght@300 For some reason this may not load in Roll20. Instead, try omitting wght@ from the snippet. example: Open+Sans+Condensed:300

A quick template for use to construct your own rule.

@import url('https://fonts.googleapis.com/css?family=NAME+OF+FONT+YOU+WANT&display=swap');

You can then call the font from inside your CSS with the font-family-attribute:

// imports the "Sigmar One" font to the sheet & rolltemplates
@import url('https://fonts.googleapis.com/css?family=Sigmar+One&display=swap');

.charsheet span{
    // sets all span to use "Sigmar One", and "Arial" is made a fallback font, if Sigmar doesn't work
    font-family: 'Sigmar One', Arial;
}

[edit] Material Icons

You can use Google's Material Icons-font, but it works slightly differently than other google fonts. You import the font together with rest of the google fonts, but instead of defining a font-family for when you want to use it, instead you just add material-icons as a class, and then write the icons name in the element, or use the special character for it.

List of material Icons & their names

Minimal working example:

<span class="material-icons">cloud</span>
<span class="material-icons blue">face</span>
<span class="material-icons"></span>
@import url('https://fonts.googleapis.com/css?family=Material+Icons&display=swap');

.charsheet .blue{
  color: blue;
}
Useful icons for Roll20 Sheets
* Tracking: star-outline, star-half, star, favorite, favorite_border, key, money, ticket-star, power, power_off, diamond
    • Toggle toggle_on, toggle_off, check_box, check_box_outline_blank
    • battery, battery-flash, battery-unknown, battery-alert, battery_0_bar - battery_6_bar, battery_full
    • thumb-up, thumb-down, thumb-up-down
  • Status Effects & Similar: hospital, healing, fire, severe_cold, flare, flash, fire, bolt, waves, air, shield-security, flower, cloud-outline, flower-alt, sun, walk, run, mood, mood-sad, accounts(great for duplicate/mirror image), volume-off, hearing, hearing-disabled hotel(rest symbol}}, mic-off(silenced effect?), balance, sick, speed, thermostat, auto_fix_high(magic wand), emergency, heart_broken, water_drop
  • Travel/Downtime: cutlery, compass, landscape, park, nature, map, hiking, hearing, liqour, home, castle, church, forest, grass, fort, local_police, mosque, sailing, temple_buddhist, temple_hindu, warehouse
  • Settings, config, menu: flag, menu, more, more-vert, settings, wrench, help, alert-circle, alert-triangle, info, help, close-circle-o, close-circle, close, push_pin
    • functions(edit macros?)
    • colors, visibility: palette, eye, eye-off, format-color-fill, invert-colors, invert-colors-off
    • light/dark: light_mode, mode_night, lamp, brightness-2(moon), brightness-5(sun)
    • upload, download, print, smartphone
  • Misc Modern: portable-wifi-changes(looks like a radar), bug, radio, router, sacnner input-antenna, wifi-alt, car, city, cocktail, boat, cafe, gas-satation, map, traffic, devices, globe, rocket, satellite_alt, search, cell_tower, nightlife, science, psychology, vaccines, coronavirus
  • Contribute/Sheet Dev: github, translate

[edit] Custom Fonts

It's still possible to use custom fonts, but it's not guaranteed to work and requires the user who want to see them to disable browser brower security to allow "unsecure scripts" to either allow Roll20 to search for locally installed fonts or import a font from an URL.

Starfinder HUD is an example of a sheet using custom font, and having a fallback font if the user doesn't allow unsafe scripts.

@font-face{/*The aldrich font*/
    font-family:Aldrich;
    src:local('Aldrich'),local('Aldrich-Regular'),url(https://kurohyou.github.io/Starfinder-Character-Sheet/Sheet%20Fonts/Aldrich-Regular.ttf);
}

.charsheet * {
    // sets Aldrich to be used on the whole sheet, & adds "Copperplate" & "Monospace" as backup fonts if Aldrich fails to load.
    font-family:Aldrich,Copperplate,Monospace;
}

In the segment above, @font-face defines the name and source of the font. It first asks the broswer if the font exists locally, and if it isn't locally installed, the url gives a location to download the url from.

[edit] iframe

Since mid-2021, you are able to style small parts outside the sheet it self, namely the Bio&Info-tab, A&A-tab, and the buttons to navigate between them.

Here is an example that will:

  • make the Bio&Info/Character Sheet/A&A buttons stay wisible at the top of your sheet even when you scroll down the sheet
  • make the A&A-button slightly less noticable
  • minor adjustments on the A&A-tab
.characterviewer .content {
  margin-bottom: 0;
}
.characterviewer .nav-tabs {
  padding-top: 2px;
  min-width: 351px;
  margin-bottom: 0;
}
.characterviewer .nav-tabs > li:nth-child(2) {
  -webkit-box-ordinal-group: 0;
      -ms-flex-order: -1;
          order: -1;
}
.characterviewer .nav-tabs > li:nth-child(4) > a:nth-child(1) {
  opacity: 0.3;
}
.characterviewer .nav-tabs > li:nth-child(4) > a:nth-child(1):hover, .characterviewer .nav-tabs > li:nth-child(4) > a:nth-child(1):focus {
  opacity: 1;
}

.ui-dialog .largedialog {
  padding: 0px 5px;
}
.ui-dialog .charsheet {
  padding: 0;
}

div#tab-content.tab-content .attributesabilities {
  min-width: 525px;
}
div#tab-content.tab-content .attributesabilities .largedialog textarea {
  border: 2px solid grey;
  background: #ececec;
}

[edit] Tricks and Effects

Tricks and other more involved methods for create effects on your sheet.

[edit] Show/Hide Areas


Charsheet-show-hide-checkbox.gif

Live Demo

You can show or hide areas on the character sheet based on the state of a checkbox. Instead of the adjacent sibling selector (+) used by custom checkboxes, you should use the sibling selector (~).

On the 'toggle-show' checkbox, the following 'body' is hidden when the toggle is not checked.

On the 'toggle-hide' checkbox, the following 'body' is hidden when the toggle is checked.

<div class="sheet-columns">
  <div>
      <input type="checkbox" class="sheet-toggle-show" />
      <span>Show</span>
      <div class="sheet-body">You found me!</div>
  </div>
  <div>
      <input type="checkbox" class="sheet-toggle-hide" />
      <span>Hide</span>
      <div class="sheet-body">Hey, a little privacy here?!</div>
  </div>
</div>
.charsheet .sheet-columns {
  display: flex;
  justify-content: space-between;
  width: 400px;
}
.charsheet .sheet-columns > * {
  flex: 1;
}

.charsheet input.sheet-toggle-show:not(:checked) ~ div.sheet-body,
.charsheet input.sheet-toggle-hide:checked ~ div.sheet-body {
    display: none;
}

[edit] Swap Visible Areas

Character-sheets-swap-area.gif

Live Demo

You can apply the hide areas logic to multiple elements based on the same checkbox, and get swappable behavior:

<div>
  <input type="checkbox" class="sheet-block-switch" name="attr_block_switch" value="1">
  <div class="sheet-block-a">
    Lorem ipsum dolor sit amet
  </div>
  <div class="sheet-block-b">
    consectetur adipiscing elit
  </div>
</div>
.sheet-block-a,
.charsheet .sheet-block-switch:checked ~ .sheet-block-b {
  display: block;
}

.charsheet .sheet-block-b,
.charsheet .sheet-block-switch:checked ~ .sheet-block-a {
  display: none;
}

[edit] Tabs

Tabs-example.gif

Many paper-versions of character sheets have more than one page, and among the best ways to implement this in Roll20 sheets is to create tabs for each page, swapping the visible area with some form of buttons. The following examples are the main two ways this can be done, where the first one is more streamlined than the older example, and is using much more of an modern approach, leading to less problems on older/less used browsers.

Button-based tabs

This is a short and simple example of implementing tabs on a character sheet using action buttons and sheetworkers, created by Finderski & GiGs.

<input type="hidden" class="sheet-tabstoggle" name="attr_sheetTab"  value="character"  />
<div>
    <button type="action" name="act_character" >Character</button>
    <button type="action" name="act_journal" >Journal</button>
    <button type="action" name="act_configuration" >Configuration</button>
</div>

<div class="sheet-character">
    <h2>Character</h2>
    <span>character Stuff Goes here </span>
</div>
<div class="sheet-journal">
    <h2>Journal/Notes</h2>
    <span>Journal/Notes Stuff Goes here</span> 
</div>
<div class="sheet-config">
    <h2>Config/Settings</h2>
    <span>Sheet Config/Settings goes here</span> 
</div>

<script type="text/worker">
    const buttonlist = ["character","journal","configuration"];
    buttonlist.forEach(button => {
        on(`clicked:${button}`, function() {
            setAttrs({
                sheetTab: button
            });
        });
    });
</script>
/*Configure the tab buttons*/
.charsheet .sheet-character,
.charsheet .sheet-config,
.charsheet .sheet-journal {
    display: none;
}

/* show the selected tab */
.charsheet .sheet-tabstoggle[value="character"] ~ div.sheet-character,
.charsheet .sheet-tabstoggle[value="configuration"] ~ div.sheet-config,
.charsheet .sheet-tabstoggle[value="journal"] ~ div.sheet-journal {
    display: block;
}

Be warned that the location of the hidden input named attr_sheetTab, relative to the div containing the tabs matters. The hidden input must immediately precede the div containing the tabs as in the example above, otherwise it may not work.

[edit] radio input + span .method

An Older Tab Example using invisible radio inputs + spans to create the "buttons"

Note: This example/method of creating tabs on a character sheet does not work out of the box, contains lots of unnecessary code, and is far less intuitive than the new tab example. This example is generally not recommended to use, but is left here just for the sake of continuity, and those who might need troubleshoot their implementation. Ambition & Avarice and Hc Svnt Dracones 2E are two sheets that base their tabs on this older "invisible radio input + span"-method, and both needed manual adjustment on at least the position of the tabs to actually work.

Old sheets with bad tabs: Lastly, a number of older sheet's using a variation of this older "input + span"-method don't render correctly on Firefox, and instead shows just the larger radio buttons, but doesn't show the name of the tab. These currently broken tab-implementations could be changed to use either this working version of the "input + span"-method or to the newer "action button" example.

I suspect these older "input + span" implementations where broken by some minor change that Firefox did to how their display certain elements, and due to this method being IMO a pretty ugly hack codewise that people wouldn't probably use on normal HTML/CSS, it's understandable our "hack" eventually stopped working. Andreas J. (talk) 12:11, 17 May 2021 (UTC)
Old Tab example(Doesn't work as-is, lots of redundant code, not intuitive)

Live demo A tabbed layout is essentially an extension of hidden areas, using radio inputs instead of checkbox inputs.

<input type="radio" name="attr_tab" class="sheet-tab sheet-tab1" value="1" checked="checked"><span title="First Tab"></span>
<input type="radio" name="attr_tab" class="sheet-tab sheet-tab2" value="2"><span title="Second Tab"></span>
<input type="radio" name="attr_tab" class="sheet-tab sheet-tab3" value="3"><span title="Third Tab"></span>
<input type="radio" name="attr_tab" class="sheet-tab sheet-tab4" value="4"><span title="Fourth Tab"></span>

<div class="sheet-tab-content sheet-tab1">
    <h1>Tab 1</h1>
    Lorem ipsum dolor sit amet
</div>

<div class="sheet-tab-content sheet-tab2">
    <h1>Tab the Second</h1>
    consectetur adipisicing elit
</div>

<div class="sheet-tab-content sheet-tab3">
    <h1>3rd Tab</h1>
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua
</div>

<div class="sheet-tab-content sheet-tab4">
    <h1>Fourth Tab</h1>
    Ut enim ad minim veniam
</div>
div.sheet-tab-content { display: none; }

input.sheet-tab1:checked ~ div.sheet-tab1,
input.sheet-tab2:checked ~ div.sheet-tab2,
input.sheet-tab3:checked ~ div.sheet-tab3,
input.sheet-tab4:checked ~ div.sheet-tab4 { display: block; }

input.sheet-tab {
    width: 150px;
    height: 20px;
    position: relative;
    top: 5px;
    left: 6px;
    margin: -1.5px;
    cursor: pointer;
    z-index: 1;
    opacity: 0;
}

input.sheet-tab + span::before {
    content: attr(title);
    border: solid 1px #a8a8a8;
    border-bottom-color: black;
    text-align: center;
    display: inline-block;
    background: #fff;
    background: linear-gradient(to top, #c8c8c8, #fff, #c8c8c8);
    width: 150px;
    height: 20px;
    font-size: 18px;
    position: absolute;
    top: 12px;
    left: 13px;
}

input.sheet-tab:checked + span::before {
    background: #dcdcdc;
    background: linear-gradient(to top, #fcfcfc, #dcdcdc, #fcfcfc);
    border-bottom-color: #fff;
}

input.sheet-tab:not(:first-child) + span::before { border-left: none; }

input.sheet-tab2 + span::before {
    background: #fee;
    background: linear-gradient(to top, #f8c8c8, #fee, #f8c8c8);
    left: 163px;
}

input.sheet-tab2:checked + span::before {
    background: #dcdcdc;
    background: linear-gradient(to top, #fcecec, #f8c8c8, #fcecec);
    border-bottom-color: #fcecec;
}

input.sheet-tab3 + span::before { left: 313px; }

input.sheet-tab4 + span::before { left: 463px; }

div.sheet-tab-content {
    border: 1px solid #a8a8a8;
    border-top-color: #000;
    margin: 2px 0 0 5px;
    padding: 5px;
}

div.sheet-tab2 { background-color: #fcecec; }

The key to take away from this is that we have a set of radio buttons which are siblings to the divs that contain each tab's content. Then, we hide all of the tabs' content and use the sibling selector along with the :checked property to show the tab content associated with that particular radio button.

The rest of this example shows off means to make your tabs look pretty, such as using content: attr(title) to set the text of the tab, giving them colors and borders, and even making certain tabs different from the others in some fashion.

[edit] Dropdown

Sometimes it makes more sense to hide/show areas of the sheet with a dropdown(<select>), especially if there are lots of options, which would make buttons or radio inputs take too much space.

Example: If the game ExampleRPG have three classes/archetypes, and each have very different looking sheets compared to the others, it would make sense to have a dropdown which you'd use to change which archetype's section is shown. If you put the whole code into the character-section of the above Tabs example, you could still have multiple pages for your sheet, where the other pages/tabs are identical, and only some of the content inside the character-page would change depending on the <select>.


With the <select> directly changing the values, there is no need for the sheetworker that was used in the Tabs example.


<div>
  <label>Archetype:<label>
  <select name="attr_archetype">
    <option value="fighter" selected>fighter</option>
    <option value="mage">mage</option>
    <option value="rogue">rogue</option>
  </select>
</div>

<div class="character">
//remember that this works only if .sheet-archetypetoggle and .sheet-fighter, .sheet-mage and .sheet-rogue are siblings elements and put in the correct order 
<input type="hidden" class="sheet-archetypetoggle" name="attr_archetype"  value="fighter"  /> 
    <input name="attr_character_name" type="text" /> <!-- name is shown regardless of selected archetype -->

    <div class="sheet-fighter">
        <h2>fighter</h2>
        <span>Fighter weapon specialization here</span>
    </div>
    <div class="sheet-mage">
        <h2>mage</h2>
        <span>Mage spellcasting & magic here</span> 
    </div>
    <div class="sheet-rogue">
        <h2>rogue</h2>
        <span>Rogue espertise and skill specialization here</span> 
    </div>
</div>
/* By deafult, hides all archetypes*/
.charsheet .sheet-fighter,
.charsheet .sheet-mage,
.charsheet .sheet-rogue {
    display: none;
}

/* show the selected archetype */
.charsheet .sheet-archetypetoggle[value="fighter"] ~ div.sheet-fighter,
.charsheet .sheet-archetypetoggle[value="mage"] ~ div.sheet-mage,
.charsheet .sheet-archetypetoggle[value="rogue"] ~ div.sheet-rogue {
    display: block;
}

[edit] Pop Up Boxes

With the additional support for <a> links and IDs using , Roll20 has also given an example of how this functionality can be used to create pop up content using the :target selector.

[edit] Cycling Button

It's sometimes useful to have a single control(like button/radio input) that the user can click on to cycle through a series of values/options, instead of creating a <select>-dropdown.

Old example (pure html+css)

Live Demo

You can't make a button that rotates through a list, changing a displayed value. However, you can fake it!

The trick lies in layering the radio buttons on top of one another, and changing the z-index based on which input is checked.

  • First, set all of the inputs to some baseline z-index.
  • Set the first radio input of the group to a z-index higher than the rest.
  • For each input, when it is checked, set the z-index of the next input to something higher than the first input. You don't need to do anything for when the last input is selected, as they'll all be back at their default z-index (which means the first one is on top).

When the first input is selected, only the second one will be visible, so you can't click on any other value. When the second is clicked, the third will become the only one you can click on, and so on. The user can also navigate back and forth using arrow keys, or a combination of the tab key (or shift+tab) and the space bar.

<div class="sheet-damage-box">
    <input type="radio" class="sheet-damage-box sheet-no-damage" name="attr_damage-box" value="0" checked>
    <input type="radio" class="sheet-damage-box sheet-bashing-damage" name="attr_damage-box" value="1">
    <input type="radio" class="sheet-damage-box sheet-lethal-damage" name="attr_damage-box" value="2">
    <input type="radio" class="sheet-damage-box sheet-aggravated-damage" name="attr_damage-box" value="3">

    <span class="sheet-damage-box sheet-no-damage">☐ (no damage)</span>
    <span class="sheet-damage-box sheet-bashing-damage">&nbsp;/&nbsp; (bashing damage)</span>
    <span class="sheet-damage-box sheet-lethal-damage">☓ (lethal damage)</span>
    <span class="sheet-damage-box sheet-aggravated-damage">✱ (aggravated damage)</span>
</div>
.charsheet div.sheet-damage-box {
    width: 195px;
    height: 30px;
    position: relative;
}

.charsheet input.sheet-damage-box {
    width: 30px;
    height: 30px;
    position: absolute;
    z-index: 1;
}

.charsheet span.sheet-damage-box {
    margin: 10px 0 0 40px;
    display: none;
}

.charsheet input.sheet-no-damage { z-index: 2; }

.charsheet input.sheet-no-damage:checked + input.sheet-bashing-damage,
.charsheet input.sheet-bashing-damage:checked + input.sheet-lethal-damage,
.charsheet input.sheet-lethal-damage:checked + input.sheet-aggravated-damage { z-index: 3; }

.charsheet input.sheet-no-damage:checked ~ span.sheet-no-damage,
.charsheet input.sheet-bashing-damage:checked ~ span.sheet-bashing-damage,
.charsheet input.sheet-lethal-damage:checked ~ span.sheet-lethal-damage,
.charsheet input.sheet-aggravated-damage:checked ~ span.sheet-aggravated-damage { display: inline-block; }

As you can see, this uses the show/hide areas technique to display a span with some text for each radio input. You could also display an image, an input field, an entire section of the charactersheet, whatever you like.

One fancy option would be to combine this with the technique to style your radio buttons. Hide the radios with opacity: 0, and display an image in the same location as the radio button (make sure the image is at a lower z-index than the invisible buttons!) so that the user is apparently clicking on the displayed image to change it.

This uses the same approach described in Checkbox and Radio Input Styling: it uses a hidden attribute to store the value and a styled input/button to update the value.

This trick is used on multiple WoD sheets to cycle through showing different types of damage symbols in their health boxes.

The key distinction here is incrementing the numeric value by 1 each time the button is clicked and using the Modulo Operator to cycle the value back to 0 once it reaches 4. This makes it cycle through 0, 1, 2, and 3 before going back to 0.

<div>
  <input type="hidden" name="attr_health_1" class="sheet-health" value="0" />
  <button type="action" name="act_health_1_cycle" class="sheet-health"></button>

  <span class="sheet-no-damage">☐ (no damage)</span>
  <span class="sheet-bashing-damage"> /  (bashing damage)</span>
  <span class="sheet-lethal-damage">☓ (lethal damage)</span>
  <span class="sheet-aggravated-damage">✱ (aggravated damage)</span>
</div>
<script type="text/worker">
  on(`clicked:health_1_cycle`, () => {
      // Check the current value of the hidden attribute.
    getAttrs(["health_1"], (v) => {
      const healthValue = parseInt(v["health_1"]) || 0;
      // Increment the attribute value by 1, or cycle back to 0 if the incremented value is equal to 4.
      setAttrs({
        "health_1": (healthValue + 1) % 4
      });
    });
  });
</script>
/* Configure the button styling. This example makes it look like a checkbox. */
.charsheet button.sheet-health {
  width: 10px;
  height: 10px;
  padding: 0;
  border: solid 1px #000000;
  background: #efefef;
}

/* Draw a diagonal slash if the hidden attribute value is "1" */
.charsheet input.sheet-health[value="1"] + button.sheet-health {
  background:
    linear-gradient(to top left,
      #efefef 0%,
      #efefef calc(50% - 0.8px),
      rgba(0,0,0,1) 50%,
      #efefef calc(50% + 0.8px),
      #efefef 100%);
}
/* Draw two diagonal slashes if the hidden attribute value is "2" */
.charsheet input.sheet-health[value="2"] + button.sheet-health {
  background:
    linear-gradient(to top left,
      rgba(0,0,0,0) 0%,
      rgba(0,0,0,0) calc(50% - 0.8px),
      rgba(0,0,0,1) 50%,
      rgba(0,0,0,0) calc(50% + 0.8px),
      rgba(0,0,0,0) 100%),
    linear-gradient(to top right,
      #efefef 0%,
      #efefef calc(50% - 0.8px),
      rgba(0,0,0,1) 50%,
      #efefef calc(50% + 0.8px),
      #efefef 100%);
}
/* Draw three diagonal slashes if the hidden attribute value is "3" */
.charsheet input.sheet-health[value="3"] + button.sheet-health {
  background:
    linear-gradient(to top left,
      rgba(0,0,0,0) 0%,
      rgba(0,0,0,0) calc(50% - 0.8px),
      rgba(0,0,0,1) 50%,
      rgba(0,0,0,0) calc(50% + 0.8px),
      rgba(0,0,0,0) 100%),
    linear-gradient(to top right,
      rgba(0,0,0,0) 0%,
      rgba(0,0,0,0) calc(50% - 0.8px),
      rgba(0,0,0,1) 50%,
      rgba(0,0,0,0) calc(50% + 0.8px),
      rgba(0,0,0,0) 100%),
    linear-gradient(to right,
      #efefef 0%,
      #efefef calc(50% - 0.8px),
      rgba(0,0,0,1) 50%,
      #efefef calc(50% + 0.8px),
      #efefef 100%);
}

/* Hide content that does not correspond with the hidden attribute's value. */
.charsheet input.sheet-health[value="0"] ~ :not(.sheet-health):not(.sheet-no-damage) {
  display: none;
}
.charsheet input.sheet-health[value="1"] ~ :not(.sheet-health):not(.sheet-bashing-damage) {
  display: none;
}
.charsheet input.sheet-health[value="2"] ~ :not(.sheet-health):not(.sheet-lethal-damage) {
  display: none;
}
.charsheet input.sheet-health[value="3"] ~ :not(.sheet-health):not(.sheet-aggravated-damage) {
  display: none;
}


[edit] Reset Styles

example by Scott

Doing this or similar will remove all or most of Roll20's default styling applied to these, so you can avoid more unwanted behavior when you write your own css

"You can use unset instead of initial to really unset it, but that's a little harder than I like because you wind up with radios and checkboxes with appearance:none on them, which is useful sometimes, but other times not so much." - Scott
.ui-dialog .charsheet select,
.ui-dialog .charsheet textarea,
.ui-dialog .charsheet input,
.ui-dialog .charsheet .uneditable-input,
.ui-dialog .charsheet label,
.ui-dialog .charsheet button{
  all: initial;
}

[edit] Disable Click

Example of some inputs disabled with pointer-events: none;(visual glitch unrelated)

You can use pointer-events or user-select to disable the user from selecting/interacting an element on the sheet with their mouse. It works for at least <select>, text/number/checkbox/radio inputs & <button>.

Note: Neither pointer-events nor user-select stops users from selecting the element using the TAB-key.

.sheet-noclick{
  pointer-events: none;  
}

Then you can use some other condition to turn this feature on/off, such as a checkbox. This trick have been used on many sheet.

Example:

<div>
    <label>disabled</label>
    <span>
        <input type="text" name="attr_name" class="sheet-noclick">
    </span>
    <span>
        <input type="number" name="attr_lvl" class="sheet-noclick">
    </span>
    <span>
        <input type="checkbox" name="attr_check" value="1" class="sheet-noclick">
    </span>
    <span>
        <input type="radio" name="attr_radio" value="0" class="sheet-noclick">
        <input type="radio" name="attr_radio" value="1" class="sheet-noclick">
    </span>
    <select name="attr_WoundLevel" class="sheet-noclick">
      <option value="0" selected="selected">Healthy</option>
      <option value="1">Stunned</option>
      <option value="1">Wounded</option>
      <option value="2">Wounded Twice</option>
      <option value="5">Incapacitated</option>
      <option value="10">Mortally Wounded</option>
    </select>
    <button type="roll" value="/roll 1d20" name="roll_init" class="sheet-noclick">init</button>
</div>
<div>
    <label>normal</label>
    <span>
        <input type="text" name="attr_name">
    </span>
    <span>
        <input type="number" name="attr_lvl">
    </span>
    <span>
        <input type="checkbox" name="attr_check" value="1">
    </span>
    <span>
        <input type="radio" name="attr_radio" value="0">
        <input type="radio" name="attr_radio" value="1">
    </span>
    <select name="attr_class">
         <option value="0" selected="selected">Warrior</option>
         <option value="1">Mage</option>
         <option value="2">Expert</option>
    </select>
    <button type="roll" value="/roll 1d20" name="roll_init">init</button>
</div>
.charsheet select.sheet-noclick,
.charsheet input[type="text"].sheet-noclick,
.charsheet input[type="number"].sheet-noclick,
.charsheet input[type="checkbox"].sheet-noclick,
.charsheet input[type="radio"].sheet-noclick,
.charsheet input[type="text"].sheet-noclick,
.charsheet button[type="roll"].sheet-noclick{
  pointer-events: none;  
}

[edit] Dark/Light Mode

You can create an option for your sheet that can change the full color scheme of the sheet. The "Savage Worlds Tabbed" and "GURPS" sheet are two examples that have these options.

[edit] Custom Progress Bar

To create a custom progress bar, one can use CSS and sheetworkers to update an hidden field. See also: Creating a progress bar/health bar on the Character Sheet(Forum) for more examples.

[edit] Example 1

Andreas J.'s example:
Sheet-hp-bar.gif
<div class="health-bar">
  <span>HP:</span>
  <input name="attr_hp" class="rangetest" type="range" min="0" max="10" value="10">
  <input name="attr_hp" class="rangetest" type="number" value="10">
</div>
 

<pre data-language="css" >
input[type="range"].sheet-rangetest{
  width: 100px;
}
/* works only on firefox atm */
input[type="range"].sheet-rangetest::-moz-range-thumb{
  background: transparent; 
  border-color: transparent;
  color: transparent;
}
input[type="range"].sheet-rangetest::-moz-range-progress{
  background: green;
  height: 10px;
}
input[type="range"].sheet-rangetest::-moz-range-track{
  background: red;
  height: 10px;
}

[edit] Example 2

Leothedino's exampleLive Demo

Leothedino have created a more sophisticated progress bar(Forum), where you can change the current and max value for what is displayed, and a sheetworker then scales the numbers to fit the bar.

<div class="sheet-container">
<input type="hidden" value="2" name="attr_Character_overweight" class="sheet-hidden sheet-overweight">
<div class="sheet-overweight"></div>
</div>
div.sheet-overweight {
    width: 90%;
    height: 20px;
    border: 1px solid black;
    color: black;
    text-align: center;
}
input.sheet-overweight[value="0"] ~ div.sheet-overweight {
    background: white;
}
input.sheet-overweight[value="1"] ~ div.sheet-overweight {
    background: linear-gradient(to left, white 60%, green 75%);    
}
input.sheet-overweight[value="2"] ~ div.sheet-overweight {
    background: linear-gradient(to left, white 40%, yellow 75%);
}
    input.sheet-overweight[value="2"] ~ div.sheet-overweight:before {
        content:"Bags half full";
    }
input.sheet-overweight[value="3"] ~ div.sheet-overweight {
    background: linear-gradient(to left, white 20%, orange 75%);
}
    input.sheet-overweight[value="3"] ~ div.sheet-overweight:before {
        content: "Bags nearly full";
    }
input.sheet-overweight[value="4"] ~ div.sheet-overweight {
    background: linear-gradient(to left, white, red 100%);
}
    input.sheet-overweight[value="4"] ~ div.sheet-overweight:before {
        content: "Bags full !";
    }
input.sheet-overweight[value="5"] ~ div.sheet-overweight {
    background: black;
    color: white;
}
    input.sheet-overweight[value="5"] ~ div.sheet-overweight:before {
        content: "Bags too heavy !";
}

[edit] Example 3

Scott C.'s example:
Progress bar.gif

Scott has created another sophisticated progress bar here(Forum).

This looks pretty complicated, but the benefit is that we can use this styling and sheetworker for any progress bar setup we might make and don't need to redo the CSS or javascript for specific steps, attribute names, or maximum values. Additionally, if you're using a templating library like PUG, Mustache, or handlebars to build the html of your sheet, this setup can be built via a bit of resuable code instead of needing to copy/paste the html each time you need it.

[edit] Hexagons

Live demo

<div class="sheet-hex sheet-hex-3" style="background-color: #444; width: 100px; height: 57px">     
    <div class="sheet-inner">
        <h4>Stat</h4>
        <input type="number" style="width: 50%">
    </div>      
    <div class="sheet-corner-1"></div>
    <div class="sheet-corner-2"></div>      
</div>
.sheet-hex {
    width: 100px;
    height: 57px;
    background-color: #ccc;
    background-repeat: no-repeat;
    background-position: 50% 50%;           
    background-size: auto 173px;                            
    position: relative;
    float: left;
    margin: 25px 5px;
    text-align: center;
    zoom: 1;
}

.sheet-hex.sheet-hex-gap {
    margin-left: 86px;
}

.sheet-hex a {
    display: block;
    width: 100%;
    height: 100%;
    text-indent: -9999em;
    position: absolute;
    top: 0;
    left: 0;
}

.sheet-hex .sheet-corner-1,
.sheet-hex .sheet-corner-2 {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: inherit;                                
    z-index: -2;                        
    overflow: hidden;       
    backface-visibility: hidden;            
}

.sheet-hex .sheet-corner-1 {
    z-index:-1;
    transform: rotate(60deg);
}

.sheet-hex .sheet-corner-2 {
    transform: rotate(-60deg);
}

.sheet-hex .sheet-corner-1::before,
.sheet-hex .sheet-corner-2::before {
    width: 173px;
    height: 173px;
    content: '';
    position: absolute;
    background: inherit;
    top: 0;
    left: 0;
    z-index: 1;
    background: inherit;
    background-repeat: no-repeat;
    backface-visibility: hidden;                  
}

.sheet-hex .sheet-corner-1::before {
    transform: rotate(-60deg) translate(-87px, 0px);    
    transform-origin: 0 0;
}

.sheet-hex .sheet-corner-2::before {
    transform: rotate(60deg) translate(-48px, -11px);   
    bottom: 0;
}

/* Custom styles*/
.sheet-hex .sheet-inner { color: #eee; }

.sheet-hex h4 {
    font-family: 'Josefin Sans', sans-serif;        
    margin: 0;          
}

.sheet-hex hr {
    border: 0;
    border-top: 1px solid #eee;
    width: 60%;
    margin: 15px auto;
}

.sheet-hex p {
    font-size: 16px;
    font-family: 'Kotta One', serif;
    width: 80%;
    margin: 0 auto;
}

.sheet-hex.sheet-hex-1 { background: #74cddb; }
.sheet-hex.sheet-hex-2 { background: #f5c53c; }
.sheet-hex.sheet-hex-3 { background: #80b971; }

Note: Make sure the hexagon's width is 57% of the hexagon's height.

[edit] Clocks

Live Demo

CSS gradients can do all kinds of interesting things. Here's an example of creating a clock-shaped representation of a value:

<div class="sheet-harm-section">
  <input type="radio" value="0" name="attr_harm" class="sheet-harm-checkbox sheet-harm-0" checked>0
  <input type="radio" value="1" name="attr_harm" class="sheet-harm-checkbox sheet-harm-3">3
  <input type="radio" value="2" name="attr_harm" class="sheet-harm-checkbox sheet-harm-6">6
  <input type="radio" value="3" name="attr_harm" class="sheet-harm-checkbox sheet-harm-9">9
  <input type="radio" value="4" name="attr_harm" class="sheet-harm-checkbox sheet-harm-10">10
  <input type="radio" value="5" name="attr_harm" class="sheet-harm-checkbox sheet-harm-11">11
  <input type="radio" value="6" name="attr_harm" class="sheet-harm-checkbox sheet-harm12">12
  <hr>
  <div class="sheet-clock"></div>
</div>
.sheet-harm-section {
    display: inline-block;
    text-align: center;
}

.sheet-clock {
    width: 5em;
    height: 5em;
    display: inline-block;
    border-radius: 50%;
    background: black;
    border: 2px solid black;
}

.sheet-harm-checkbox.sheet-harm-0:checked ~ .sheet-clock {
    background: white;
}

.sheet-harm-checkbox.sheet-harm-3:checked ~ .sheet-clock {
    background-image: linear-gradient(180deg, transparent 50%, white 50%),
                      linear-gradient(90deg, white 50%, transparent 50%);
}

.sheet-harm-checkbox.sheet-harm-6:checked ~ .sheet-clock {
    background-image: linear-gradient(90deg, white 50%, transparent 50%);
}

.sheet-harm-checkbox.sheet-harm-9:checked ~ .sheet-clock {
    background-image: linear-gradient(180deg, transparent 50%, black 50%),
                      linear-gradient(90deg, white 50%, transparent 50%);
}

.sheet-harm-checkbox.sheet-harm-10:checked ~ .sheet-clock {
    background-image: linear-gradient(210deg, transparent 50%, black 50%),
                      linear-gradient(90deg, white 50%, transparent 50%);
}

.sheet-harm-checkbox.sheet-harm-11:checked ~ .sheet-clock {
    background-image: linear-gradient(240deg, transparent 50%, black 50%),
                      linear-gradient(90deg, white 50%, transparent 50%);
}

.sheet-harm-checkbox.sheet-harm-12:checked ~ .sheet-clock {
    background-color: black;
}


[edit] Special

[edit] Icon Fonts

An icon font is a font which has pictures instead of letters, and Roll20 have a number of them accessible by default. You can specify one of the icon fonts below with the font-family property. For example, something like:

<p>A Gem: <span style="font-family: 'Pictos Three'">a</span></p>

Would produce:

A Gem: a


Example:

.charsheet div.sheet-d6{
  font-family: dicefontd6;
  // makes the content of this div a black d6 showing 6 dots, by defining the character that corresponds to that symbol.
  content: "L";
}
.charsheet div.sheet-d8{
  // any div with this class will display the test as various d8 dice 
  font-family: dicefontd8;
}
<div>
 <p>At character creation, you have a dicepool of:</p>
 <p>5 <div class="sheet-d6">I</div> </p>
 <p>3 <div class="sheet-d8">h</div> </p>
 <p> bunch of d8 with different numbers<div class="sheet-d8">abcdefgh</div>
</div>

Looks about:

At character creation, you have a dicepool of:

5

I

3

h

bunch of d8 with different numbers

abcdefgh

[edit] Pictos

Character Icon Character Icon Character Icon Character Icon
 !  !  :  : S S l l
" "  ;  ; T T m m
# # < < U U n n
$ $ = = V V o o
 %  % > > W W p p
& &  ?  ? X X q q
' ' @ @ Y Y r r
( ( A A Z Z s s
) ) B B [ [ t t
* * C C \ \ u u
+ + D D ] ] v v
, , E E ^ ^ w w
- - F F _ _ x x
. . G G ` ` y y
/ / H H a a z z
0 0 I I b b { {
1 1 J J c c | |
2 2 K K d d } }
3 3 L L e e ~ ~
4 4 M M f f
5 5 N N g g
6 6 O O h h
7 7 P P i i
8 8 Q Q j j
9 9 R R k k



[edit] Pictos Custom

Character Icon
[ [
a a
e e
i i
o o
p p
q q
r r
t t
u u
w w
y y











Pictos Three

Character Icon
a a
b b
c c
d d
e e
f f
g g
h h
i i
j j
k k
l l











fontello

Character Icon
&#xe800;
&#xe801;
&#xe802;
&#xe803;
&#xe804;
&#xe805;
&#xe806;
&#xe807;
&#xe808;
&#xe809;
&#xe80a;
&#xe80b;
&#xe80c;
&#xe80d;
&#xe80e;
&#xe80f;
&#xe810;
&#xe811;
&#xe812;
&#xe813;
&#xf008;



dicefontd4

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O
P P

dicefontd6

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O q q
P P r r
Q Q
R R



















dicefontd8

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G
H H


[edit] dicefontd10

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O q q
P P r r
Q Q s s
R R t t
S S
T T

dicefontd12

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K
L L


[edit] dicefontd20

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O q q
P P r r
Q Q s s
R R t t
S S
T T

dicefontd30

Character Icon Character Icon Character Icon
0 0 L L g g
1 1 M M h h
2 2 N N i i
3 3 O O j j
4 4 P P k k
5 5 Q Q l l
6 6 R R m m
7 7 S S n n
8 8 T T o o
@ @ U U p p
A A V V q q
B B W W r r
C C X X s s
D D Y Y t t
E E Z Z u u
F F a a v v
G G b b w w
H H c c x x
I I d d y y
J J e e z z
K K f f

[edit] See Also

[edit] Guides