Adding a Description to the Carrington Sheet

This is most complex of the three columns with many parts. Let’s look at what we want it to look like (and as usual ignore the colours).

We can see there is a label, followed by a table with two columns and 4 rows, then a textbox. We can easily do the table layout using CSS Grid, yet again, and the default textbox will automatically span both columns. So, we need to figure out the height of the textarea, and look at the table part.

You can select a name, rank, whether the character is Eldritch (all PCs are), and whether the character is a PC or an NPC. The Lock button then locks these options in so you can’t accidentally change them. This is done using a combination of a sheet worker and CSS.

The HTML is below, and there is a lot going on here to discuss. We’ll break it down and look at each part.

<div class="description">
    <button type="action" name="act_biotoggle" class="title">
    <input type="hidden" name="attr_lock" class="lock-toggle" value="0">
    <span class="title">Name</span>
    <input type="text" name="attr_character_name" class="fit locked" value="" readonly>
    <input type="text" name="attr_character_name" class="fit unlocked" value="">
    <span class="title">Rank</span>
    <select name="attr_character_rank" class="fit unlocked">
       {{#each ladder}}
       {{#unless @first}}<option {{#eq this "Poor"}}selected{{/eq}}>{{this}}</option>{{/unless}}
    <input type="text" name="attr_character_rank" value="Poor" class="locked fit" readonly />
    <span class="title">Eldritch?</span>
       <input type="checkbox" name="attr_eldritch_exists" class="eldritch-on unlocked" value="1" checked>
       <input type="checkbox" name="attr_eldritch_exists" class="eldritch-on locked noclick" value="1" checked>
       <button type="action" name="act_lock" class="nod20">
          <span name="attr_lock_label">Lock?</span>
    <span class="title">Type</span>
    <div class="type">
       <select name="attr_character_type" class="fit unlocked" value="PC">
          <option selected>PC</option>
       <input type="text" name="attr_character_type" class="fit locked noclick" value="PC" readonly>
       <input type="text" name="attr_character_label" class="fit locked noclick" value="PC" readonly>
    <textarea name="attr_description" class="description" placeholder="DESCRIPTION"></textarea>
</div>Code language: Handlebars (handlebars)

Yet Another Grid

Part of the section is a two-column section, so it is no-brainer to use CSS Grid. It makes creating pseudo-tables very easy, and allows for things to fill multiple columns easy too (as in the header at the top and textarea at the bottom.

.charsheet .description {
    display: grid;
    grid-template-columns: 4em auto;
    column-gap: 5px;
    align-content: start;
}Code language: C/AL (cal)
.charsheet .description .fit {
    width: 160px;
    height: 28px;
    margin-bottom: 0;
}Code language: CSS (css)

The grid section has the addition of align-content: start. Normally, a grid will divide its rows to fit its container, but that creates an unsightly spread. Aligning the rows to the start squeezes them all into the minim space possible.

The fit class we have created here is so that we can assign common values to inputs and selects – just add that class and these values will be automatically added. This ensures that the width (among other things) of text boxes doesn’t spread into the middle column.

Using Span for Labels

It’s common to use Label element for, well, labels, but I don’t like using it. It was designed for use with forms which aren’t allowed in roll20, and the styling is different from other text. I prefer using spans for all text. I created a common class, title, allowing us to assign common styles.

Note: I was going to use the class label, but Roll20 is already using that. Sometimes there are predefined classes and it’s a good idea to avoid them – they may have CSS styling in them you aren’t expecting.

.charsheet span.title {
    font-weight: bold;
    margin-top: 5px;
}Code language: CSS (css)

Here we make labels bold, and spans are not very well aligned with inputs and selects. Giving them a top margin of 5px usually lines them up nicely.

The Locked Button

Much of the function of this section differs when locked and when unlocked. Since there are just two states, it’s easy to create a hidden input which toggles between values of 0 and 1.

We already have a sheet worker that handles toggling (biotoggle), so can just add lock to that. We want the label on the action button to change from Locked to Lock? based on the status of the lock attribute, soo another brief sheet worker is created for that. We already have the lock and lock_label attributes, now we need to fill them. We already have the foundation for most of this.

    on('change:lock sheet:opened', () => {
        getAttrs(['lock'], values => {
            const lock = +values.lock || 0;
            const lock_label = lock ? "Locked" : "Lock?";
    const tabs = {
        toggles: ['biotoggle', 'lock'],  
        inserts: {
            /* action buttons which insert the name in a specific location */
    run_tabs(tabs);Code language: JavaScript (javascript)

Doubling Up On Elements

If you want elements to exist in two states, you often need two separate copies of those elements and use CSS to hide one and display the other. There’s a lot going on here. First, we have a character_name input whose name can be changed when unlocked, but is fixed when locked. There is also a character_rank and character_type, both of which have different mechanical rules. They are presented as select dropdowns when unlocked, and readonly inputs when locked.

Finally there is an Eldritch checkbox, whose value can be changed when unlocked, but can’t be altered when locked. This effect is created by adding the CSS pointer-events: none; when locked. This refers to the mouse, and means you can’t click the checkbox and thus cant accidentally change it by clicking it. By creating a noclick class, we can add this to any relevant item which might be handy later.

.charsheet select.noclick,
.charsheet input.noclick {
    pointer-events: none;
.charsheet input.lock-toggle:not([value="1"]) ~ div .locked,
.charsheet input.lock-toggle:not([value="0"]) ~ div .unlocked,
.charsheet input.lock-toggle:not([value="1"]) ~ .locked,
.charsheet input.lock-toggle:not([value="0"]) ~ .unlocked {
    display: none;
}Code language: CSS (css)

There are different locked and unlocked items here, because they are based on the class of a specific input, and you have to trace the path to the things its class affects. Some items are in the same container, and some are inside a div inside that container.

Character Name

All character sheets in all systems have a special character_name attribute. If you change that, the visible name of the character sheet changes, so it’s a good idea to include it somewhere on the sheet. Players can enter the name of their character in one place, and it becomes the name of their character sheet. It’s very handy.

Character Rank Unless

Characters have an advancement rank which matches values on the ladder, but cannot equal terrible. So, we can use the Handlebars each list to build it. This is what that looks like with some extra nesting.

<select name="attr_character_rank" class="fit unlocked">
   {{#each ladder}}
      {{#unless @first}}
         <option {{#eq this "Poor"}}selected{{/eq}}>{{this}}</option>
</select>Code language: Handlebars (handlebars)

The unless statement here says, unless this is the first item create the option. Since Terrible is the first option, that never gets an item, so we have a dropdown that starts at Poor, which is exactly what we want.

The eq part ensures that Poor gets the selected keyword, so that is the default value, just as with Abilities.

In Conclusion

Sp there we have it, the finisghed Bio section. But there’s a lot of the sheet left to do…

Series Navigation<< The Bio Tab in the Carrington SheetManaging Experience in the Carrington Sheet >>

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.