diff --git a/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js b/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cef1bf4ba5a771ec25a0482665d14041a8f71ad
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js
@@ -0,0 +1,19 @@
+import BetaBadge from './beta_badge.vue';
+
+export default {
+  component: BetaBadge,
+  title: 'vue_shared/beta-badge',
+};
+
+const template = `
+    <div style="height:600px;" class="gl-display-flex gl-justify-content-center gl-align-items-center">
+      <beta-badge />
+    </div>
+  `;
+
+const Template = () => ({
+  components: { BetaBadge },
+  template,
+});
+
+export const Default = Template.bind({});
diff --git a/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue b/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
new file mode 100644
index 0000000000000000000000000000000000000000..198b658027704d07c23fa642687663497df332dc
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
@@ -0,0 +1,58 @@
+<script>
+import { GlBadge, GlPopover } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+  name: 'BetaBadge',
+  components: { GlBadge, GlPopover },
+  i18n: {
+    badgeLabel: s__('BetaBadge|Beta'),
+    popoverTitle: s__("BetaBadge|What's Beta?"),
+    descriptionParagraph: s__(
+      "BetaBadge|A Beta feature is not production-ready, but is unlikely to change drastically before it's released. We encourage users to try Beta features and provide feedback.",
+    ),
+    listIntroduction: s__('BetaBadge|A Beta feature:'),
+    listItemStability: s__('BetaBadge|May have performance or stability issues.'),
+    listItemDataLoss: s__('BetaBadge|Should not cause data loss.'),
+    listItemReasonableEffort: s__('BetaBadge|Is supported by a commercially reasonable effort.'),
+    listItemNearCompletion: s__('BetaBadge|Is complete or near completion.'),
+  },
+  methods: {
+    target() {
+      /**
+       * BVPopover retrieves the target during the `beforeDestroy` hook to deregister attached
+       * events. Since during `beforeDestroy` refs are `undefined`, it throws a warning in the
+       * console because we're trying to access the `$el` property of `undefined`. Optional
+       * chaining is not working in templates, which is why the method is used.
+       *
+       * See more on https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49628#note_464803276
+       */
+      return this.$refs.badge?.$el;
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <gl-badge ref="badge" class="gl-cursor-pointer">{{ $options.i18n.badgeLabel }}</gl-badge>
+    <gl-popover
+      triggers="hover focus click"
+      :show-close-button="true"
+      :target="target"
+      :title="$options.i18n.popoverTitle"
+      data-testid="beta-badge"
+    >
+      <p>{{ $options.i18n.descriptionParagraph }}</p>
+
+      <p class="gl-mb-0">{{ $options.i18n.listIntroduction }}</p>
+
+      <ul class="gl-pl-4">
+        <li>{{ $options.i18n.listItemStability }}</li>
+        <li>{{ $options.i18n.listItemDataLoss }}</li>
+        <li>{{ $options.i18n.listItemReasonableEffort }}</li>
+        <li>{{ $options.i18n.listItemNearCompletion }}</li>
+      </ul>
+    </gl-popover>
+  </div>
+</template>
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 600b49e62f158f42d7e998f84f1c40eaeeb5a618..60752ce271846346c77da14f6a8982142a279dc6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7235,6 +7235,30 @@ msgstr ""
 msgid "Beta"
 msgstr ""
 
+msgid "BetaBadge|A Beta feature is not production-ready, but is unlikely to change drastically before it's released. We encourage users to try Beta features and provide feedback."
+msgstr ""
+
+msgid "BetaBadge|A Beta feature:"
+msgstr ""
+
+msgid "BetaBadge|Beta"
+msgstr ""
+
+msgid "BetaBadge|Is complete or near completion."
+msgstr ""
+
+msgid "BetaBadge|Is supported by a commercially reasonable effort."
+msgstr ""
+
+msgid "BetaBadge|May have performance or stability issues."
+msgstr ""
+
+msgid "BetaBadge|Should not cause data loss."
+msgstr ""
+
+msgid "BetaBadge|What's Beta?"
+msgstr ""
+
 msgid "Bi-weekly code coverage"
 msgstr ""
 
diff --git a/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap b/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..5c49ea1b9f40435e59a060550136e6f822c04d50
--- /dev/null
+++ b/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap
@@ -0,0 +1,53 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Beta badge component renders the badge 1`] = `
+<div>
+  <gl-badge-stub
+    class="gl-cursor-pointer"
+    iconsize="md"
+    size="md"
+    variant="muted"
+  >
+    Beta
+  </gl-badge-stub>
+   
+  <gl-popover-stub
+    cssclasses=""
+    data-testid="beta-badge"
+    showclosebutton="true"
+    target="[Function]"
+    title="What's Beta?"
+    triggers="hover focus click"
+  >
+    <p>
+      A Beta feature is not production-ready, but is unlikely to change drastically before it's released. We encourage users to try Beta features and provide feedback.
+    </p>
+     
+    <p
+      class="gl-mb-0"
+    >
+      A Beta feature:
+    </p>
+     
+    <ul
+      class="gl-pl-4"
+    >
+      <li>
+        May have performance or stability issues.
+      </li>
+       
+      <li>
+        Should not cause data loss.
+      </li>
+       
+      <li>
+        Is supported by a commercially reasonable effort.
+      </li>
+       
+      <li>
+        Is complete or near completion.
+      </li>
+    </ul>
+  </gl-popover-stub>
+</div>
+`;
diff --git a/spec/frontend/vue_shared/components/badges/beta_badge_spec.js b/spec/frontend/vue_shared/components/badges/beta_badge_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6109fad931044bad69213aa547569369c60cfc18
--- /dev/null
+++ b/spec/frontend/vue_shared/components/badges/beta_badge_spec.js
@@ -0,0 +1,12 @@
+import { shallowMount } from '@vue/test-utils';
+import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue';
+
+describe('Beta badge component', () => {
+  let wrapper;
+
+  it('renders the badge', () => {
+    wrapper = shallowMount(BetaBadge);
+
+    expect(wrapper.element).toMatchSnapshot();
+  });
+});