<section>
  <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <path id="circlePath" fill="none" stroke-width="4" stroke="hsl(0 100% 50% / 0.5)" d="
          M 10, 50
          a 40,40 0 1,1 80,0
          a 40,40 0 1,1 -80,0
        " />
    <text id="text" font-family="monospace" font-size="12" font-weight="bold" fill="var(--text-1)">
      <textPath id="textPath" href="#circlePath"></textPath>
    </text>
  </svg>
</section>
:root {
  --gray-0: #f8f9fa;
  --gray-1: #f1f3f5;
  --gray-2: #e9ecef;
  --gray-3: #dee2e6;
  --gray-4: #ced4da;
  --gray-5: #adb5bd;
  --gray-6: #868e96;
  --gray-7: #495057;
  --gray-8: #343a40;
  --gray-9: #212529;
  --gray-10: #16191d;
  --gray-11: #0d0f12;
  --gray-12: #030507;
  /* Surfaces */
  --text-1: var(--gray-12);
  --text-2: var(--gray-10);
  --text-3: var(--gray-8);
  --text-4: var(--gray-7);
  --surface-1: var(--gray-0);
  --surface-2: var(--gray-1);
  --surface-3: var(--gray-2);
  --surface-4: var(--gray-3);
}

@media (prefers-color-scheme: dark) {
  :root {
    --text-1: var(--gray-1);
    --text-2: var(--gray-3);
    --text-3: var(--gray-5);
    --text-4: var(--gray-6);
    --surface-1: var(--gray-10);
    --surface-2: var(--gray-9);
    --surface-3: var(--gray-8);
    --surface-4: var(--gray-7);
  }
}

*,
*:after,
*:before {
  box-sizing: border-box;
}

body {
  display: grid;
  place-items: center;
  min-height: 100vh;
  font-family: 'Google Sans', sans-serif, system-ui;
  overflow: hidden;
  background: var(--surface-3);
  color: var(--text-2);
}

svg {
  width: 80vmin;
  overflow: visible;
}

path {
  opacity: var(--show, 0);
  transition: opacity 0.2s;
}

.dg :is(.cr.string, .cr.number) input[type=text] {
  color: white;
  line-height: 1;
}
import { GUI } from "https://cdn.skypack.dev/dat.gui@0.7.9"

const CIRCLE_PATH = document.querySelector('#circlePath')
const TEXT_PATH = document.querySelector('#textPath')
const TEXT = document.querySelector('#text')

const OPTIONS = {
  text: 'Your text here! •&nbsp;',
  size: 12,
  radius: 20,
  showPath: true,
  spread: false,
  inside: false,
}

const CTRL = new GUI()

const VIEWBOX = 100

const onUpdate = () => {
  TEXT.setAttribute('font-size', OPTIONS.size)
  const PATH = `
    M ${VIEWBOX * 0.5 - OPTIONS.radius}, ${VIEWBOX * 0.5}
    a ${OPTIONS.radius},${OPTIONS.radius} 0 1,${OPTIONS.inside ? 0 : 1} ${
    OPTIONS.radius * 2
  },0
    ${OPTIONS.radius},${OPTIONS.radius} 0 1,${OPTIONS.inside ? 0 : 1} -${
    OPTIONS.radius * 2
  },0
  `
  if (OPTIONS.spread) TEXT_PATH.setAttribute('textLength', Math.floor(Math.PI * 2 * OPTIONS.radius))
  else TEXT_PATH.removeAttribute('textLength')
  CIRCLE_PATH.setAttribute('d', PATH)
  TEXT_PATH.innerHTML = OPTIONS.text
  CIRCLE_PATH.style.setProperty('--show', OPTIONS.showPath ? 1 : 0)
}

CTRL.add(OPTIONS, 'text').name('Text').onChange(onUpdate)
CTRL.add(OPTIONS, 'size', 6, 16, 1).name('Font size').onChange(onUpdate)
CTRL.add(OPTIONS, 'radius', 20, 50, 1).name('Radius').onChange(onUpdate)
CTRL.add(OPTIONS, 'showPath').name('Show path').onChange(onUpdate)
CTRL.add(OPTIONS, 'spread').name('Spread text').onChange(onUpdate)
CTRL.add(OPTIONS, 'inside').name('Inside').onChange(onUpdate)

onUpdate()

// <circle
//   id="MyPath"
//   fill="none"
//   stroke="red"
//   cx="50"
//   cy="50"
//   r="25"
// />
// <path
//   d="
//     M (CX - R), CY
//     a R,R 0 1,0 (R * 2),0
//     a R,R 0 1,0 -(R * 2),0
//   "
// />
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.