Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Roll20 Tips and Tricks (Innovative Solutions to Common Problems)

Toggleable template fields, an update on reusing rolls Following on from the earlier toggleable template fields post : Attempting to reuse rolls in a hide-able section would prevent that section from being hidden. However this problem can be avoided by using HTML entities to partially escape any roll reference. So $[[0]] becomes $[[0]]. Reusing a roll this way doesn't interact with the rest of the roll parsing making it safe to use for our purposes here. (Note: It only works when using roll templates) Toggleable template fields, roll controlled Those familiar with reusing rolls know that trying to reuse a roll before it happens breaks any roll it was nested inside of. This behaviour can be used to control if a set of template fields is inside a working roll or not, since a "broken" roll won't hide the fields but a working roll will. By using a dynamic roll reference to conditionally reference a nested roll within a hide-able section. That hide-able section can then be hidden or shown based on a roll result. A single section example In this example 2d4 is rolled and on double one will show "Snake Eyes!" after the roll plus a "Bonus Section" $[[$[[1]]]] [[[ [[ {0,[[2d4]]}=2*2 ]] ]]] &{template:default} {{2d4=$[[0]]}} [[[0 [[0]] {{2d4=$[[0]] Snake Eyes!}} {{Bonus Section}}]]] A multi section example In this example a d6 is rolled that highlights a roll of 1 and a roll of 6 with bonus d6 $[[$[[1]]]] [[[ [[ {1,6,6}=[[d6]]*2 ]] ]]] &{template:default} {{d6=$[[0]]}} [[[0 [[0]] {{d6=$[[0]] One!}} ]]] [[[0 [[0]] {{d6=$[[0]] Six!}} {{Bonus d6=$[[5]]}} ]]] [[d6 Bonus]]
1718225889
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
You never cease to astound, Rainbow Encoder!
1721308921

Edited 1721320164
Collapsing rows and Bleeding CSS to next row in a template As it's been noted, using CSS styling in a standard template can be a bit tricky, because the template uses = as a separator in the content rows. But, by some clumsy experimentation, I accidentally managed to break the rows to allow CSS to be applied to the left side of the equation (with a caveat I'll get to). Here's how: First, we make our CSS row - this row needs not have content, though it can have some - what we care about is the end: {{=[placeholder](#" style=" whatever style declarations you want; class="showtip" title=)}} You'll notice the empty tooltip trick at the end - this is important. Next, the content row: {{[the left side, with the CSS styling from above](#)=The right side}} Putting it all together... &{template:default} {{=[placeholder](#" style=" color: white; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; background-image: linear-gradient(to right, red,orange,yellow,green,blue,indigo,violet; class="showtip" title=)}} {{[Rainbow](#)=Not Rainbow!}} Ta-dah! The first row has disappeared (or more specifically, collapsed into the second, but the broken link is not displayed - if there's other content on that row, it'll replace the left  side of the bottom row, either as a normal left side entry, if left of the =, or as an indented, stylable right-left - I haven't fully explored what you can do with that), and the CSS styling has been applied to the left side of the second row! Now, you'll notice the caveat I mentioned - there's a very noticeable indent on the left side of the template - the left side starts at about the same position as the right would in a normally partitioned template. However, the left side is still fully functional as a row name, meaning you can use overwrite trick to all kinds of clever effects, from hiding the name fields with a display:none;  to shifting around the CSS bleed rows, to maybe even more clever things with the row-collapsing trick. Go wild! EDIT: You can even add a tooltip to the left-side field using this trick, by placing the whole tooltip trick before the collapse trick: &{template:default} {{=[placeholder](#" style=" color: white; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; background-image: linear-gradient(to right, red,orange,yellow,green,blue,indigo,violet; " class="showtip" title= boop class="showtip" title=)}} {{[Rainbow](#)=Not Rainbow!}} Relatedly, you can daisy chain collapsed rows to turn multiple rows into files on a single row instead, like so: &{template:default} {{[=Let's go![collapser](#" style=" display: none; class="showtip" title=)}} {{[1](#)=first file content goes here[ collapser ](#" style=" display: none; class="showtip" title=)}}{{[2](#)=second file content goes here [ collapser ](#" style=" display: none; class="showtip" title=)}}{{[3](#)=third file content goes here [ collapser ](#" style=" display: none; class="showtip" title=)}} {{[4](#)=fourth file content goes here}} Though the template dimensions can get pushed out of the right side of the chat if there's too many too wide files. If you do not hide the left elements, they appear at the end of the file to the left of its content (think of them as an index starting with 0, with 0 being the empty file at the left). EDIT: I figured out how to solve the indent issue (or rather, how to apply the trick to the actual first row name) - it's as simple as putting the collapser at the end of the name space, like so: &{template:default} {{name=testing stuff[placeholder](#" style=" display: none; class="showtip" title=)}}{{[0](#)=[first file content goes here](#" style=" font-weight: normal; display: block; color: white; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; background-image: linear-gradient(to right, red,orange,yellow,green,blue,indigo,violet;)[placeholder](#" style=" display: none; class="showtip" title=)}} {{[1](#)=second file content goes here[placeholder](#" style=" display: none; class="showtip" title=)}}{{[2](#)=third file content goes here[placeholder](#" style=" display: none; class="showtip" title=)}}{{[3](#)=fourth file content goes here}} The text defaults to bold in the first file, but you can use CSS styling to make it look however you like - it's content on the left side of the = on its row, so if you like, you can style every single letter differently. This also means you can make a full width text field in a template , where you can style individual segments as you wish. TLDR; this tech 1. collapses a template row (but not the namespace) into the next row 2. applies the CSS declarations to the name (left side) of the next row and 3. allows you to place the content of a row where the name of the row would be, or vice versa, and create extra files in the row.
1721315515
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Thanks for breaking that long dry spell, Tuo, with a real whizbanger!
Health Bars in Chat Given an attribute with a known maximum, such as health in most games, you can make use of reusing rolls to display them as bars, by overlaying elements with position: absolute;, anchoring the elements to the left (or right, if you prefer) side of the chat with left: 0px; and using the reused roll containing the desired value, divided by the max value and multiplied by 100, for width: %;, and the original "roll" is hidden by placing it outside template rows, like so: &{template:default}{{name=[ ](#" style=" background-color: black; padding: 10px 0px 10px; margin-top: -5px; display: block; width: 100%;  position: absolute; left: 0px)[ ](#" style=" background-color: green; padding: 10px 0px 10px; margin-top: -5px; display: block; width: $[[0]]%;  position: absolute; left: 0px)}} [[ [[?{health out of 100?}/100*100]] ]] (example of multiple simple green bars placed in chat one after another) With that basic format, you can adjust the declarations to your heart's content, add new elements and variables to make more complex and fancier bars. Here's a much more complex example, featuring health bar changing color as it gets lower, and supporting multiple health bars laid on top of each other, using @{hp} character attribute, max hp defined in @{Cinematic Weight|hp} (due to the game I wrote this macro for), @{bars} and it's sheet-defined max for the bar count (accessed through some nested attribute calls, because calling max values in abilities is needlessly complicated if you want to use the exact same macro across multiple sheets without changing attribute references). &{template:default}{{name=[ ](#" style=" background-color: black; padding: 10px 0px 10px; margin-top: -5px; display: block; width: 100%;  position: absolute; left: 0px; z-index: 1)[ ](#" style=" opacity: $[[19]]%; padding: 10px 0px 10px; margin-top: -5px; display: block; width: 110%;  position: absolute; left: 0px; z-index: 1; background-image: linear-gradient(#995400, #995400, #eb7900, #eb7900, #eec890)[ ](#" style=" opacity: $[[18]]%; padding: 10px 0px 10px; margin-top: -5px; display: block; width: 110%;  position: absolute; left: 0px; z-index: 1; background-image: linear-gradient(#636300, #636300, #c7b000, #c7b000, #eded91)[ ](#" style=" opacity: 30%; padding: 10px 0px; border-left: 4px solid lightgrey; margin-top: -5px; display: block; width: $[[20]]%;  position: absolute; right: 0px; z-index: 2; background-image: linear-gradient(to right, black, transparent, transparent)[ ](#" style=" opacity: 30%; padding: 10px 0px; border-left: 4px solid lightgrey; margin-top: -5px; display: block; width: $[[21]]%;  position: absolute; right: 0px; z-index: 2; background-image: linear-gradient(to right, black, transparent, transparent, transparent)[ ](#" style=" padding: 10px 0px; margin-top: -5px; display: block; width: $[[12]]%;  position: absolute; left: 0px; z-index: 2; background-image: linear-gradient(darkred, darkred, red, red, #ee9090)[ ](#" style=" opacity: $[[1]]%; padding: 10px 0px; margin-top: -5px; display: block; width: $[[13]]%;  position: absolute; left: 0px; z-index: 3; background-image: linear-gradient(darkgreen, darkgreen, green, green, lightgreen)[ ](#" style=" opacity: $[[7]]%; padding: 10px 0px; margin-top: -5px; display: block; width: $[[14]]%;  position: absolute; left: 0px; z-index: 3; background-image: linear-gradient(#636300, #636300, #c7b000, #c7b000, #eded91)[ ](#" style=" opacity: $[[11]]%; padding: 10px 0px; margin-top: -5px; display: block; width: $[[15]]%;  position: absolute; left: 0px; z-index: 3; background-image: linear-gradient(#995400, #995400, #eb7900, #eb7900, #eec890)[ ](#" style=" opacity: 85%; padding: 5px 0px; margin-top: -5px; display: block; width: $[[16]]%;  position: absolute; left: 0px; z-index: 3; background-image: linear-gradient(white, transparent, transparent)[ ](#" style=" opacity: 60%; padding: 10px 0px; margin-top: -5px; display: block; width: $[[17]]%;  position: absolute; left: 0px; z-index: 3; background-image: linear-gradient(to bottom left, white, transparent, transparent, transparent)[ ](#" style=" border-left: 4px ridge lightgrey; border-right: 4px ridge lightgrey; padding: 10px 0px; margin-top: -5px; display: block; position: absolute; left: 0px; right: 0px; z-index: 3; background-color: transparent)[@{hp}/@{Cinematic Weight|hp}](#" style=" cursor: default; text-decoration: none; color: white; background-color: transparent; padding: 6px 0px 3px 5px; margin-top: -8px; display: block; width: 110%;  position: absolute; left: 0px; z-index: 4)[ ](#" style=" cursor: pointer; opacity: 0%; padding: 10px 0px 12px; margin-top: -5px; display: block; width: 110%;  position: absolute; left: 0px; z-index: 5)}}$[[1]] $[[7]] $[[11]] $[[18]] $[[19]] $[[12]] $[[13]] $[[14]] $[[15]] $[[16]] $[[17]] opacity1_1[[  [[@{hp}/@{Cinematic Weight|hp}*200*[[{@{bars}-@{atrtag}|bars|max}+1,0}kh1]]]] ]] opacity2_7[[  [[[[@{hp}/@{Cinematic Weight|hp}*200]]*[[floor(1/[[abs([[[[@{bars}-@{atrtag}|bars|max}]]+1]])+1]])]]]] ]] opacity3_11[[ [[[[@{hp}/@{Cinematic Weight|hp}*200]]*[[floor([[abs(@{bars}-@{atrtag}|bars|max})]]/2)]]]] ]] width_12[[ [[@{hp}/@{Cinematic Weight|hp}*100]] ]] width_13[[ [[@{hp}/@{Cinematic Weight|hp}*100]] ]] width_14[[ [[@{hp}/@{Cinematic Weight|hp}*100]] ]] width_15[[ [[@{hp}/@{Cinematic Weight|hp}*100]] ]] width_16[[ [[@{hp}/@{Cinematic Weight|hp}*100]] ]] width_17[[ [[@{hp}/@{Cinematic Weight|hp}*100]] ]] yellow_backbar_18[[ [[{@{bars}-@{atrtag}|bars|max}+1,0}kh1*{@{bars}-1,1}kl1*100]] ]] orange_backbar_19[[ [[{@{atrtag}|bars|max}-2,1}kl1*{@{bars}-1,1}kl1*100]] ]] inverse-width_20[[ [[99-@{hp}/@{Cinematic Weight|hp}*100]] inverse-width_21[[ [[99-@{hp}/@{Cinematic Weight|hp}*100]]
1721753692
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
That could make for a very nice party readout for a healer character. Very impressive!
1723015783

Edited 1723015953
Using queries to carry variable data to a follow-up macro Following up on building an entire macro within a chat roll button, you can have a macro print data inside a query option, and then use the property of queries to copy the input to all copies of the query to reuse that data in as many places within the second macro as you like. Here's a testable example, replicating the function of a roll table: &{template:default} {{= [click here](`/ooc &{template:default} ?{confirm|ok,$[[0]]|cancel,{{} {{[1](#)= Option 1}} {{[[[{1d0+?{confirm}}>10]]](#)= Option 2}} {{[[[{1d0+?{confirm}}>20]]](#)= Option 3}} {{[[[{1d0+?{confirm}}>30]]](#)= Option 4}} {{[[[{1d0+?{confirm}}>40]]](#)= Option 5}} {{[[[{1d0+?{confirm}}>50]]](#)= Option 6}}{{[[[{1d0+?{confirm}}>60]]](#)= Option 7}}{{[[[{1d0+?{confirm}}>70]]](#)= Option 8}}{{[[[{1d0+?{confirm}}>80]]](#)= Option 9}}{{[[[{1d0+?{confirm}}>90]]](#)= Option 10}} {{[0](#)=?{confirm}}})}}  [[ [[1d100]] ]] Here, a d100 is rolled, called early to access a plain numeric result, that result is within a query, and when that query is resolved, when the second macro is activated, that result is applied into the nested rolls across the macro. This is getting around the issue of a roll being able to be operated on only once - in the second macro, every copy of that result from the first macro can be operated on individually. Of course, for this particular use case. a roll table is much more practical, but this is more flexible - you can print whatever you like within the query, and it'll be used just fine.
Tuo said: Using queries to carry variable data to a follow-up macro Following up on building an entire macro within a chat roll button, you can have a macro print data inside a query option, and then use the property of queries to copy the input to all copies of the query to reuse that data in as many places within the second macro as you like... This is a useful technique for carrying text data across macros. For purely numerical data you can use Reusing Rolls with the caveat that any random roll has to be wrapped in another inline roll. So in your example instead of pulling the plain numeric result via $[[0]] and copying via queries you could actually just use $[[1]] wherever it's needed and skip the query. This also allows you to use the value in other rolls without problems. That being said this can be used to overwrite queries from existing macros/abilities using earlier data. For example consider the following macro #rollWithMod &{template:default} {{Roll=$[[0.computed]] + $[[1.computed]] = [[ [[1d20]] + [[ [[?{Modifier|d4|d6|d8|d10|d12}]] ]] ]]}} {{=[Roll Again](` ?{Modifier|Modifier Result ($[[2]]),$[[2]]|d4|d6|d8|d10|d12} #rollWithMod)}} When run this macro will query for a modifier of d4 to d12. Rolls 1d20 + the selected modifier showing the individual rolls and sum result. Finally showing a prompt to 'Roll Again'. When 'Roll Again' is clicked it will call the macro again but pre-empts the query and inserts a new default option of the prior modifier result allowing the user to keep the same modifier roll and just reroll the d20.
RainbowEncoder said: This is a useful technique for carrying text data across macros. For purely numerical data you can use Reusing Rolls with the caveat that any random roll has to be wrapped in another inline roll. So in your example instead of pulling the plain numeric result via $[[0]] and copying via queries you could actually just use $[[1]] wherever it's needed and skip the query. This also allows you to use the value in other rolls without problems. I don't see how - a roll cannot be iterated on multiple times, so if you want to compare the same roll against multiple different targets, calling the same roll index doesn't work - and the non-plain numeric results break the escaped secondary macro. The plain numeric result copied via the query isn't in the roll index, so it doesn't have the same limitations.
Tuo said: I don't see how - a roll cannot be iterated on multiple times, so if you want to compare the same roll against multiple different targets, calling the same roll index doesn't work - and the non-plain numeric results break the escaped secondary macro. The plain numeric result copied via the query isn't in the roll index, so it doesn't have the same limitations. You are correct in that a roll index cannot be used multiple times in a single macro and a random roll will break the escaped secondary macro. However a calculation-only roll does work, which is why you have to wrap any random roll in an additional roll making it calc-only. Specifically a calc-only roll generates HTML that gets stripped out by the sanitiser leaving only the numeric result; whereas the HTML for a random roll doesn't get correctly removed leaving a bunch of junk which is what breaks attempts to use it.
RainbowEncoder said: You are correct in that a roll index cannot be used multiple times in a single macro and a random roll will break the escaped secondary macro. However a calculation-only roll does work, which is why you have to wrap any random roll in an additional roll making it calc-only. Specifically a calc-only roll generates HTML that gets stripped out by the sanitiser leaving only the numeric result; whereas the HTML for a random roll doesn't get correctly removed leaving a bunch of junk which is what breaks attempts to use it. Oh, I see - that is a great tip, I'll make sure to stick it to memory, thank you!
thank you for this post!
1724204206
GiGs
Pro
Sheet Author
API Scripter
This isn't the thread for that kind of thing - it's specifically for finished methods that people might not be aware of. You should open a new thread asking about that.
1724213323

Edited 1725490409
Hello, Roll20 geniuses. Thank you all for this treasure trove! I've lost a lot of time happily poking through here, and have learned so much about r20 and slick tricks to bust out for my friends in game. :) I hope this is okay to post in the whole thread as it's relevant to the OG index post. May I humbly request a cleanup of amazing-at-the-time tricks which may no longer be useful? I'm not sure how many that would apply to. I was checking out Robert S' trick for representing conditions with tokens . I realized, however, that there is now both a way to link to the compendium in game and a native round counting tool in the Turn Tracker (at least in the r20 D&D 5e games). I realize that could become overly complicated with the different sheets. Perhaps a footnote/asterisk if something is still useful in some sheets/systems but not others, if that's not too unwieldy. For the noobs like me who are still learning the ins and outs of r20 itself, sheets, and macros, it would be a big time saver. To be fair, going down that rabbit hole led to others where I learned more. Thanks for considering! ETA: I didn't mean they should be removed—only demarcated in some fashion. That could look like an archive section in the index post (clearest and lowest effort if universal), a shared footnote, or even adding a parenthetical to the title to note that some part of it is now native to the r20 5e sheet, for example. Just some way for those of us who are still learning the intricacies to filter out what may be more or less salient for our more pressing needs. I'm sure I'm not the only one who did a deep dive into trying to understand a trick only to later realize it could have been waaayyy down on my reading list. Honestly, the "archive" subsection is more like a Best Of—the tricks so good that r20 and sheet-designers baked them in. Thanks again to keithcurtis and everyone else for sharing your know-how and wisdom with those of us who are muddling through. :)
1724259980
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
The index does need some love, since the last few pages have not been added. I am not likely to remove anything, since even a trick that has become obsolete may reveal techniques that could be used to solve other problems. But I'll take a look soon™.
I'm glad it's still up. I'm taking a day to review a page each week to glean ideas and up my game :)