JSFiddle - React, Tailwind, and code Playground

HTML

<div id="spec" class="spec">
<!-- Piece specs go in here. Samples are shown below. Be careful: whitespace sensitive! -->

ABCD  a
      b   Oo
      c   oo
      d

 E    h
 F  efg   hg   GFE
 GH        f   H
           e

 I           IJK
 J   l         L
LK   kji  kl
          j
          i
                   t
 OP   p     QR    rs
MN    on     ST   q
       m
 W             z
 XY    zxw    yx     Y
 Z      y      w    WXZ
</div>
<div class="spec">
<!-- These are the piece specs for COTO's C 72 answer. -->

"    e    m   "   T   )   6    };  ii   3"   (.    \nn  {6"  ]"0  i%s
i    t    p   ,   &   m   1    -]  ar    "\   f(    t    8    e    T
s   )"    .[  0x  ,f  ai  0r
"

 ""   (t   ["
)"   61   4+
</div>

<!-- Edit below at your own risk. -->
<div class="proto-tet">
	<!-- I Pieces -->
	<table class="tet tet-I tet-I0" rr="tet-I1" pat="[1,0],[2,0],[3,0]">
		<tr><td>@11</td><td>@22</td><td>@33</td><td>@44</td></tr>
	</table>
	<table class="tet tet-I tet-I1" rr="tet-I2" pat="[0,1],[0,2],[0,3]">
		<tr><td>@11</td></tr>
		<tr><td>@22</td></tr>
		<tr><td>@33</td></tr>
		<tr><td>@44</td></tr>
	</table>
	<table class="tet tet-I tet-I2" rr="tet-I3" >
		<tr><td>@40</td><td>@30</td><td>@20</td><td>@10</td></tr>
	</table>
	<table class="tet tet-I tet-I3" rr="tet-I0">
		<tr><td>@40</td></tr>
		<tr><td>@30</td></tr>
		<tr><td>@20</td></tr>
		<tr><td>@10</td></tr>
	</table>

	<!-- J Pieces -->
	<table class="tet tet-J tet-J0" rr="tet-J1" pat="[0,1],[-1,2],[0,2]">
		<tr><td></td><td>@11</td></tr>
		<tr><td></td><td>@22</td></tr>
		<tr><td>@33</td><td>@44</td></tr>
	</table>
	<table class="tet tet-J tet-J1" rr="tet-J2" pat="[0,1],[1,1],[2,1]">
		<tr><td>@31</td><td></td><td></td></tr>
		<tr><td>@42</td><td>@23</td><td>@14</td></tr>
	</table>
	<table class="tet tet-J tet-J2" rr="tet-J3" pat="[1,0],[0,1],[0,2]">
		<tr><td>@41</td><td>@32</td></tr>
		<tr><td>@23</td><td></td></tr>
		<tr><td>@14</td><td></td></tr>
	</table>
	<table class="tet tet-J tet-J3" rr="tet-J0"...

CSS

.proto-tet, .spec {
    display: none;
}

.tet-I { color: darkgreen; }
.tet-J { color: orangered; }
.tet-L { color: navy; }
.tet-T { color: darkred; }
.tet-O { color: darkcyan; }
.tet-S { color: darkviolet; }
.tet-Z { color: darkorange; }

body > .tet {
    position: absolute;
    cursor: move;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    border-collapse: collapse;
}

.tet td {
    width: 18px;
    height: 18px;
    font: bold 14px "Courier New",monospace;
    text-align: center;
    vertical-align: middle;
    padding: 0;
}

.error {
    z-index: 1024;
    position: absolute;
    display: none;
    color: red;
    font-weight: bold;
    background-color: white;
}

JavaScript

var G = {
	eDrag: null,
	isIgnoreClick: true,
	iSpawn: 0
};

function parseSpec( s ) {
	var P, S = s.split('\n').map( s_ => [...s_] );
	var oPats = $('.proto-tet [pat]').get().map( e => {
		return {
			sPat: eval( '[' + $(e).attr('pat') + ']' ),
			e: e,
			block: function( P )
				{ return [at( P ), ...this.sPat.map( P_ => at( [P[0]+P_[0],P[1]+P_[1]] ) )]; },
			wipe: function( P_ )
				{ this.sPat.forEach( P_ => wipe( [P[0]+P_[0],P[1]+P_[1]] ) ); },
			match: function( P_ )
				{ return !/\s/.test( this.block( P_ ).join('') ); }
		};
	} );

	function first() {
		var x_, y_;
		y_ = S.findIndex( s_ => ((x_ = s_.findIndex( c_ => /\S/.test( c_ ) )) != -1) );

		return y_ == -1 ? null : [x_,y_];
	}

	function at( P_ ) {
		var x_ = P_[0],
			y_ = P_[1];

		return y_ >= 0 && y_ < S.length && x_ >= 0 && x_ < S[y_].length ? S[y_][x_] : ' ';
	}

	function wipe( P_ ) {
		var x_ = P_[0],
			y_ = P_[1];

		if( y_ >= 0 && y_ < S.length && x_ >= 0 && x_ < S[y_].length )
			S[y_][x_] = ' ';
	}

	window.oPats = oPats;
	while( P = first() ) {
		var oPat = oPats.find( o_ => o_.match( P ) );

		if( !oPat ) {
			orphan( at( P ) );
			wipe( P );
			continue;
		}
		createPiece( oPat.e, oPat.block( P ) );
		wipe( P );
		oPat.wipe( P );
	}
}

function createPiece( e, sChars ) {
	var sNormChars = [];

	function ondown( eEvt ) {
		var oPos = $(this).position();

		G.isIgnoreClick = false;
		G.eDrag = this;
		G.iOffsets = [eEvt.clientX - oPos.left, eEvt.clientY - oPos.top];
	}

	function onclick() {
		if( G.isIgnoreClick )
			return;

		var $E = $(this);

		prep( $('.proto-tet .' + $E.attr('rr')), (_,s1_) => sNormChars[s1_-1], $E.css('left'), $E.css('top') );
		$E.remove();
	}

	function prep( $R, fxRep, sLeft, sTop ) {
		$R.clone().html( $R.html().replace( /@(\d)(\d)/g, fxRep ) ).
			appendTo( 'body' ).
			on( 'mousedown', ondown ).
			click( onclick ).
			css( { left: sLeft, top: sTop } );
	}

	prep( $(e), (_,s1_,s2_) => (sNormChars[s1_-1] = sChars[s2_-1]), (18 + (G.iSpawn%8)*18*4) +...