I think I've found a way to dynamically modify almost any element in a block of HTML posted to Roll20 chat, using standard API buttons.  Essentially, it's just a matter of using a negative value for margin-top to shift the second block of text up and over the original.  Without even having to use z-indexing, the second block of text (including all of its buttons and other fun stuff) effectively replaces the first. You can easily test this with the code below; just copy it into a fresh API file, run it, and type "!send <height in pixels>".  Then click the buttons to see the overlays replace each other.  The key line is this one: let  topMargin  = -1 * ( state . OverlayTest . iteration  ===   1   ?   18   :  height )  - 2   *  borderSize ; If it's the first block, the top margin is set to -18px (which lines it up with the top of the chat message container).  If it's a subsequent overlay, the top margin is set to the negative of the block's height.  (In both cases, you have to account for top and bottom borders, as well as padding and such should you use them.) The applications are pretty exciting, since (if this works in real play, rather than just the sterile test bed I've created), you can use this to effect any dynamic changes you want by redrawing the HTML block with your updates. Some Issues: If another player types something, or any other chat message comes through, the relative positioning of the overlays will obviously fail.  A few things I'm considering (all of which treat the interrupting chat messages as things to be discarded, on the theory that they can resubmit once the "roll-in-progress" is finished resolving): Activating an on("chat: message") event during the time that a script is in between overlays, which adds negative spacers to the bottoms of incoming messages in an attempt to shunt things back into the proper position. Looking into ways to temporarily block all chat messages (or "font-size: 0", or something) during those periods between overlays. Trying to track the number of lines of text that's come through, derive the vertical pixel shift from that, and updating the margin-top value of the overlay to compensate. Responding immediately to any chat message by reposting the overlay block in its current state, so that it is always positioned as the most-recent message in the chat log. As a last resort, simply including a big red "ROLL IN PROGRESS; DO NOT SUBMIT TO CHAT" warning at the bottom.  This would certainly work in my group (we use voice chat and know each other fairly well), but I'd still like to find something a bit more robust. Every five or so posts, the ID of the poster (blank, in the case of this script) is prepended to the post.  This changes the height, and causes the previous overlay to creep up about 15 pixels.  This only affects a small strip of previous overlay becoming visible at the top, however (the positions of all of your updated elements remain fixed), so I'm thinking designing an ambiguous/fuzzy top edge to mask this flaw will be plenty good enough for me. The full code is below; it should work right out of the box (again, the command is "!send 300", or whatever height in pixels you'd like to try out). Please reply if you see any problems in using this more broadly, in a real-game scenario; this is just an early proof-of-concept. var  OverlayTest  =  OverlayTest  ||   ( function   ()   {      'use strict' ;      var  isPositiveInteger  =   function   ( str )   {          let  n  =  Math . floor ( Number ( str ));          return  n  !==  Infinity  &&  String ( n )   ===  str  &&  n  >   0 ;      };      var  resetTest  =   function   ()   {         state . OverlayTest  =   {             iteration :   1 ,             colorIndex :   0 ,             color :   "red" ,             bgColor :   "darkred" ,             colorSequence :   [ "red" ,   "orange" ,   "purple" ,   "cornflowerblue" ,   "yellow" ,   "pink" ,   "green" ],             fgColorSequence :   [ "palevioletred" ,   "peachpuff" ,   "lavenderblush" ,   "navy" ,   "darkgoldenrod" ,   "papayawhip" ,   "darkseagreen" ],             bgColorSequence :   [ "darkred" ,   "orangered" ,   "darkviolet" ,   "navy" ,   "darkgoldenrod" ,   "mediumorchid" ,   "darkseagreen" ]          };          return ;      };      var  incrementTest  =   function   ()   {         state . OverlayTest . iteration ++;         state . OverlayTest . colorIndex  =   ( state . OverlayTest . colorIndex  +   1 )   %  state . OverlayTest . colorSequence . length ;         state . OverlayTest . color  =  state . OverlayTest . colorSequence [ state . OverlayTest . colorIndex ];         state . OverlayTest . bgColor  =  state . OverlayTest . bgColorSequence [ state . OverlayTest . colorIndex ];          return ;      };      //#region Event Handlers (handleInput)      const  handleInput  =   function   ( msg )   {          let  args  =  msg . content . split ( /\s+/ );          if   (! args [ 0 ]. includes ( "!send" )   &&   ! msg . who . includes ( "|" ))             resetTest ();          if   ( args . shift ()   !==   "!send"   ||  msg . type  !==   "api" )              return ;          if   (! isPositiveInteger ( args [ 0 ]))   {             sendChat ( 'USAGE' ,   "Syntax:<br/><br/><b>!send <pixel height></b><br/>(Height must be greater than 90.)<br/><br/>E.g. <i>!send 300</i>" );              return ;          }          let  height  =  parseInt ( args . shift (),   10 );          let  buttonName  =   "Click for Overlay #"   +   ( state . OverlayTest . iteration  +   1 );          let  borderSize  =   3 ;          let  color  =  state . OverlayTest . color ;          let  bgColor  =  state . OverlayTest . bgColor ;          let  topMargin  =   ( state . OverlayTest . iteration  ===   1   ?   18   :  height )   +   2   *  borderSize ;          let  intMargins  =  height  /   5 ;          let  topPadding  =   ( height  -   ( 2   *  intMargins )   -   75 )   /   2 ;          let  intHeight  =  height  -   ( 2   *  intMargins )   -  topPadding ;          let  output  =              '<div name="fullContainer" style="'   +                  'margin: -'   +  topMargin  +   'px 0px -7px -45px !important;'   +                  'padding: 0px 0px !important;'   +                  'height: '   +  height  +   'px !important;'   +                  'width: 120% !important;'   +                  'display: block !important;'   +                  'background-color: black !important;'   +                  'border: '   +  borderSize  +   'px '   +  color  +   ' outset;"'   +              '>'   +                  '<div name="rollResult" style="'   +                      'height: '   +  intHeight  +   'px !important;'   +                      'width: 90% !important;'   +                      'margin: '   +  intMargins  +   'px 5% !important;'   +                      'padding-top: '   +  topPadding  +   'px !important;'   +                      'display: inline-block !important;'   +                      'background-color: '   +  color  +   ' !important;"'   +                  '>'   +                      '<span style="'   +                          'color: '   +  color  +   ';'   +                          'width: 100%;'   +                          'height: 15px;'   +                          'padding: 10px 0px;'   +                          'font-size: 24px;'   +                          'display: block;'   +                          'text-align: center;'   +                          'background-color: '   +  bgColor  +   ';'   +                          'font-weight: bold;"'   +                      '>'   +                          'OVERLAY #'   +  state . OverlayTest . iteration  +                      '</span>'   +                      '<span style="'   +                          'height: 40px;'   +                          'width: 100%;'   +                          'display: block;'   +                          'text-align: center;'   +                          'background-color: '   +  bgColor  +   ';'   +                          'font-weight: bold;"'   +                      '>'   +                          '['   +  buttonName  +   '](!send '   +  height  +   ')'   +                      '</span>'   +                  '</div>'   +              '</div>' ;         incrementTest ();         sendChat ( '|' ,  output );      };      //#endregion      //#region Public Functions: RegisterEventHandlers      const  initialize  =   function   ()   {         log ( "INITIALIZING OverlayTest." );         resetTest ();      }      const  registerEventHandlers  =   function   ()   {         on ( 'chat:message' ,  handleInput );      };      return   {         Initialize :  initialize ,         RegisterEventHandlers :  registerEventHandlers      };      //#endregion }()); on ( "ready" ,   function   ()   {      'use strict' ;     OverlayTest . Initialize ();     OverlayTest . RegisterEventHandlers ();     log ( "OverlayTest: Ready!" );     sendChat ( 'OVERLAY TEST READY' ,   "Begin test with:<br/><br/><b>!send <pixel height></b><br/>(Height must be greater than 90.)" ); });