Tab Container Demo

by Nicholas Berlette

HTML

<tab-container>
    <div role="tablist">
<button type="button" id="tab-one" role="tab" tabindex="0" aria-selected="true"><span>Input (code)</span></button>
<button type="button" id="tab-two" role="tab" tabindex="-1"><span>Output (SVG)</span></button>
<button type="button" id="tab-three" role="tab" tabindex="-1"><span>About</span></button>
    </div>
    <div role="tabpanel" aria-labelledby="tab-one" id="panel-one">
			<textarea id="input" class="input" style="width:90%; height:125px;border-color:#aaa;border-radius:4px;margin:1em 4%;"></textarea>
			<button type="button" style="padding:0.4rem 0.8rem;margin:0 1.2rem 1rem;" class="render-svg">Render SVG</button>
      <br><br>
      <div id="output-rendered"></div>
    </div>
    <div role="tabpanel" aria-labelledby="tab-two" id="panel-two" hidden>
		<pre><code id="output">// Enter some code in the previous panel and it will be rendered here to an SVG!</code></pre>
    </div>
    <div role="tabpanel" aria-labelledby="tab-three" id="panel-three" hidden>
      <pre><code>Uses shiki to highlight and render code</code></pre>
    </div>
  </tab-container>
<script src='https://unpkg.com/[email protected]'></script>
<script src='https://unpkg.com/[email protected]'></script>
<script>
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);

let highlighter, svgRenderer; 

document.querySelector('.render-svg').addEventListener("click", async () => {
  highlighter ??= await shiki.getHighlighter({
    theme: 'nord',
    preload: ["typescript", "ts", "js", "tsx", "jsx", "html", "vue-html", "xml"],
  });

  svgRenderer ??= await shiki.getSVGRenderer({
    bg: '#2E3440',
    fontFamily: 'IBM Plex Mono',
    fontSize: 14
  });

  const code = $('#input').textContent;
	if (code.length > 0) {
		const tokens = highlighter.codeToThemedTokens(code, 'typescript');
		const out = await svgRenderer.renderToSVG(tokens);

		$('#panel-two').innerHTML = out;
    $('#output-rendered').innerHTML =...

CSS

body {
	max-width: 40em;
	margin: 5rem auto 2rem auto;
	font-family: sans-serif;
	font-size: 0.8em;
}

pre, code {
	font-family: 'Operator Mono SSm Lig', 'Operator Mono Lig', 'Operator Mono', 'Dank Mono', 'MonoLisa', 'Fira Code', 'Roboto Mono', 'Ubuntu Mono', monospace !important;
	font-size: 1.05em;
}

/* Borrowed from https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html */

[role="tablist"] {
  margin: 0 0 -.1em 1em;
  overflow: visible;
}

[role="tab"] {
  position: relative;
  margin: 0;
  padding: .45em 0.85em;
  border: 1px solid hsl(219, 1%, 72%);
  border-radius: .23em .23em 0 0;
  box-shadow: 0 0 .4em hsl(219, 1%, 85%);
  overflow: visible;
  font-family: system-ui, sans-serif !important;
	letter-spacing: -0.015em;
	font-weight: bold;
  font-size: inherit;
  background: hsl(220, 20%, 94%);
	transition: all 0.4s;
	cursor: pointer;
	text-align: center;
}
[role="tab"] > span {
	text-align: center;
	margin: 0 10px;
}
/* [role="tab"] > span::after {
	background-color: #222;
	mask-image: url("https://icns.imgix.net/cross.svg");
	-webkit-mask-image: url("https://icns.imgix.net/x.svg?stroke=1px");
	content: '';
	width: 8px;
	height: 8px;
	position: absolute;
	top: 55%;
	right: 10px;
	transform: translate(0, -50%);
	border-radius: 50%;
} */
/* [role="tab"]:hover > span::after {
	outline: 4px solid #fff;
} */
[role="tab"]:not([aria-selected="true"]):not(:focus):not(:hover):not(:active) {
	color: #0008;
}
[role="tab"]::before {
	border-radius: 8px 8px 0 0;
	border-top: 3px solid #6669;
	position: absolute;
	left: -1px;
	right: -1px;
	bottom: 100%;
	transition: all 0.33s;
	content: '';
}

[role="tab"]:hover::before,
[role="tab"]:focus::before,
[role="tab"][aria-selected="true"]::before {
  border-top-color: hsl(20, 80%, 98%);
}

[role="tab"][aria-selected="true"] {
  border-radius: 0;
  background: hsl(220, 43%, 99%);
  outline: 0;
	color: #000f;
}

[role="tab"][aria-selected="true"]:not(:focus):not(:hover)::before {
  border-top-color: hsl(218,...