diff --git a/app/soapbox/components/progress_circle.js b/app/soapbox/components/progress_circle.js
new file mode 100644
index 000000000..da20515c1
--- /dev/null
+++ b/app/soapbox/components/progress_circle.js
@@ -0,0 +1,62 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+class ProgressCircle extends React.PureComponent {
+
+  static defaultProps = {
+    radius: 12,
+    stroke: 4,
+  }
+
+  render() {
+    const { progress, radius, stroke, title } = this.props;
+
+    const progressStroke = stroke + 0.5;
+    const actualRadius = radius + progressStroke;
+    const circumference = 2 * Math.PI * radius;
+    const dashoffset = circumference * (1 - Math.min(progress, 1));
+
+    return (
+      <div className={classNames('progress-circle', { 'progress-circle--over': progress > 1 })} title={title}>
+        <svg
+          width={actualRadius * 2}
+          height={actualRadius * 2}
+          viewBox={`0 0 ${actualRadius * 2} ${actualRadius * 2}`}
+        >
+          <circle
+            className='progress-circle__circle'
+            cx={actualRadius}
+            cy={actualRadius}
+            r={radius}
+            fill='none'
+            strokeWidth={stroke}
+          />
+          <circle
+            className={classNames('progress-circle__progress')}
+            style={{
+              strokeDashoffset: dashoffset,
+              strokeDasharray: circumference,
+            }}
+            cx={actualRadius}
+            cy={actualRadius}
+            r={radius}
+            fill='none'
+            strokeWidth={progressStroke}
+            strokeLinecap='round'
+          />
+        </svg>
+      </div>
+    );
+  }
+
+}
+
+ProgressCircle.propTypes = {
+  progress: PropTypes.number.isRequired,
+  radius: PropTypes.number,
+  stroke: PropTypes.number,
+  title: PropTypes.text,
+};
+
+export default ProgressCircle;
diff --git a/app/soapbox/features/compose/components/character_counter.js b/app/soapbox/features/compose/components/character_counter.js
index d056a8240..4468b9cd6 100644
--- a/app/soapbox/features/compose/components/character_counter.js
+++ b/app/soapbox/features/compose/components/character_counter.js
@@ -1,25 +1,42 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { injectIntl, defineMessages } from 'react-intl';
 import { length } from 'stringz';
+import ProgressCircle from 'soapbox/components/progress_circle';
 
-export default class CharacterCounter extends React.PureComponent {
+const messages = defineMessages({
+  title: { id: 'compose.character_counter.title', defaultMessage: 'Used {chars} out of {maxChars} characters' },
+});
 
-  static propTypes = {
-    text: PropTypes.string.isRequired,
-    max: PropTypes.number.isRequired,
-  };
-
-  checkRemainingText(diff) {
-    if (diff < 0) {
-      return <span className='character-counter character-counter--over'>{diff}</span>;
-    }
-
-    return <span className='character-counter'>{diff}</span>;
-  }
+/**
+ * Renders a character counter
+ * @param {string} props.text - text to use to measure
+ * @param {number} props.max - max text allowed
+ */
+class CharacterCounter extends React.PureComponent {
 
   render() {
-    const diff = this.props.max - length(this.props.text);
-    return this.checkRemainingText(diff);
+    const { intl, text, max } = this.props;
+
+    const textLength = length(text);
+    const progress = textLength / max;
+
+    return (
+      <ProgressCircle
+        title={intl.formatMessage(messages.title, { chars: textLength, maxChars: max })}
+        progress={progress}
+        radius={10}
+        stroke={4}
+      />
+    );
   }
 
 }
+
+CharacterCounter.propTypes = {
+  intl: PropTypes.object.isRequired,
+  text: PropTypes.string.isRequired,
+  max: PropTypes.number.isRequired,
+};
+
+export default injectIntl(CharacterCounter);
diff --git a/app/styles/application.scss b/app/styles/application.scss
index 3503b7044..fbb50eadf 100644
--- a/app/styles/application.scss
+++ b/app/styles/application.scss
@@ -88,6 +88,7 @@
 @import 'components/aliases';
 @import 'components/icon';
 @import 'components/profile-stats';
+@import 'components/progress-circle';
 
 // Holiday
 @import 'holiday/halloween';
diff --git a/app/styles/components/compose-form.scss b/app/styles/components/compose-form.scss
index de46ea001..4b015b878 100644
--- a/app/styles/components/compose-form.scss
+++ b/app/styles/components/compose-form.scss
@@ -383,15 +383,6 @@
     .character-counter__wrapper {
       align-self: center;
       margin: 0 10px 0 auto;
-
-      .character-counter {
-        cursor: default;
-        font-family: var(--font-sans-serif), sans-serif;
-        font-size: 14px;
-        font-weight: 600;
-        color: var(--primary-text-color--faint);
-        &.character-counter--over { color: $warning-red; }
-      }
     }
   }
 
diff --git a/app/styles/components/progress-circle.scss b/app/styles/components/progress-circle.scss
new file mode 100644
index 000000000..ee96af008
--- /dev/null
+++ b/app/styles/components/progress-circle.scss
@@ -0,0 +1,17 @@
+.progress-circle {
+  display: flex;
+
+  &__circle {
+    stroke: hsla(var(--primary-text-color_hsl), 0.2);
+  }
+
+  &__progress {
+    stroke: var(--brand-color);
+  }
+
+  &--over {
+    .progress-circle__progress {
+      stroke: $warning-red;
+    }
+  }
+}