diff --git a/config/feature_flags/development/limited_access_modal.yml b/config/feature_flags/development/limited_access_modal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b567b9ce0d4fa91c1bb7dc3fb01e61a95a053392
--- /dev/null
+++ b/config/feature_flags/development/limited_access_modal.yml
@@ -0,0 +1,8 @@
+---
+name: limited_access_modal
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129790
+rollout_issue_url:
+milestone: '16.4'
+type: development
+group: group::billing and subscription management
+default_enabled: false
diff --git a/ee/app/assets/javascripts/usage_quotas/components/limited_access_modal.vue b/ee/app/assets/javascripts/usage_quotas/components/limited_access_modal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..136d818f346e6e97a4789a17e9b4869c53ec382d
--- /dev/null
+++ b/ee/app/assets/javascripts/usage_quotas/components/limited_access_modal.vue
@@ -0,0 +1,62 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+const LIMITED_ACCESS_MESSAGING = Object.freeze({
+  MANAGED_BY_RESELLER: {
+    title: s__('SubscriptionMangement|Your subscription is in read-only mode'),
+    content: s__(
+      'SubscriptionMangement|To make changes to a read-only subscription or purchase additional products, contact your GitLab Partner.',
+    ),
+  },
+  RAMP_SUBSCRIPTION: {
+    title: s__(
+      'SubscriptionMangement|This is a custom subscription managed by the GitLab Sales team',
+    ),
+    content: s__(
+      "SubscriptionMangement|If you'd like to add more seats, upgrade your plan, or purchase additional products, contact your GitLab sales representative.",
+    ),
+  },
+});
+
+export default {
+  name: 'LimitedAccessModal',
+  components: { GlModal },
+  props: {
+    limitedAccessReason: {
+      type: String,
+      // defaults to 'MANAGED_BY_RESELLER' till we have API wired
+      default: 'MANAGED_BY_RESELLER',
+      validator: (prop) => ['MANAGED_BY_RESELLER', 'RAMP_SUBSCRIPTION'].includes(prop),
+      required: false,
+    },
+  },
+  computed: {
+    limitedAccessData() {
+      return LIMITED_ACCESS_MESSAGING[this.limitedAccessReason];
+    },
+    modalTitle() {
+      return this.limitedAccessData.title;
+    },
+    modalContent() {
+      return this.limitedAccessData.content;
+    },
+    primaryAction() {
+      return {
+        text: __('Close'),
+        attributes: { variant: 'confirm' },
+      };
+    },
+  },
+};
+</script>
+<template>
+  <gl-modal
+    :action-primary="primaryAction"
+    modal-id="limited-access-modal-id"
+    :title="modalTitle"
+    data-testid="limited-access-modal-id"
+  >
+    {{ modalContent }}
+  </gl-modal>
+</template>
diff --git a/ee/app/assets/javascripts/usage_quotas/seats/components/statistics_seats_card.vue b/ee/app/assets/javascripts/usage_quotas/seats/components/statistics_seats_card.vue
index e755f1aa538aceac79c6c7dc4034180a3735cbb0..d313b943dd6ad27765b6f4e036c905599d5c23e5 100644
--- a/ee/app/assets/javascripts/usage_quotas/seats/components/statistics_seats_card.vue
+++ b/ee/app/assets/javascripts/usage_quotas/seats/components/statistics_seats_card.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlLink, GlIcon, GlButton } from '@gitlab/ui';
+import { GlLink, GlIcon, GlButton, GlModalDirective } from '@gitlab/ui';
 import {
   addSeatsText,
   seatsOwedHelpText,
@@ -10,10 +10,15 @@ import {
   seatsUsedText,
 } from 'ee/usage_quotas/seats/constants';
 import Tracking from '~/tracking';
+import { visitUrl } from '~/lib/utils/url_utility';
+import LimitedAccessModal from '../../components/limited_access_modal.vue';
 
 export default {
   name: 'StatisticsSeatsCard',
-  components: { GlLink, GlIcon, GlButton },
+  components: { GlLink, GlIcon, GlButton, LimitedAccessModal },
+  directives: {
+    GlModalDirective,
+  },
   helpLinks: {
     seatsUsedLink,
     seatsOwedLink,
@@ -60,6 +65,11 @@ export default {
       default: null,
     },
   },
+  data() {
+    return {
+      showLimitedAccessModal: false,
+    };
+  },
   computed: {
     shouldRenderSeatsUsedBlock() {
       return this.seatsUsed !== null;
@@ -67,11 +77,23 @@ export default {
     shouldRenderSeatsOwedBlock() {
       return this.seatsOwed !== null;
     },
+    shouldShowModal() {
+      return gon.features?.limitedAccessModal;
+    },
   },
   methods: {
     trackClick() {
       this.track('click_button', { label: 'add_seats_saas', property: 'usage_quotas_page' });
     },
+    handleAddSeats() {
+      if (this.shouldShowModal) {
+        this.showLimitedAccessModal = true;
+        return;
+      }
+
+      this.trackClick();
+      visitUrl(this.purchaseButtonLink);
+    },
   },
 };
 </script>
@@ -124,16 +146,17 @@ export default {
     </div>
     <gl-button
       v-if="purchaseButtonLink"
-      :href="purchaseButtonLink"
+      v-gl-modal-directive="'limited-access-modal-id'"
       category="primary"
       target="_blank"
       variant="confirm"
       class="gl-ml-3 gl-align-self-start"
       data-testid="purchase-button"
       data-qa-selector="add_seats"
-      @click="trackClick"
+      @click="handleAddSeats"
     >
       {{ $options.i18n.addSeatsText }}
     </gl-button>
+    <limited-access-modal v-if="shouldShowModal" v-model="showLimitedAccessModal" />
   </div>
 </template>
diff --git a/ee/app/controllers/ee/groups/usage_quotas_controller.rb b/ee/app/controllers/ee/groups/usage_quotas_controller.rb
index 78e128f0d7fecee4e43c4a9c83b994907ad5616d..bb728f528edac18290841364987baeb5bb7e086e 100644
--- a/ee/app/controllers/ee/groups/usage_quotas_controller.rb
+++ b/ee/app/controllers/ee/groups/usage_quotas_controller.rb
@@ -14,6 +14,7 @@ module UsageQuotasController
         before_action only: [:index] do
           push_frontend_feature_flag(:data_transfer_monitoring, group)
           push_frontend_feature_flag(:enable_hamilton_in_usage_quotas_ui, group)
+          push_frontend_feature_flag(:limited_access_modal)
         end
       end
 
diff --git a/ee/spec/frontend/usage_quotas/components/limited_access_modal_spec.js b/ee/spec/frontend/usage_quotas/components/limited_access_modal_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7437e14a9cc88be697be16e15fd31d99dea5cfc6
--- /dev/null
+++ b/ee/spec/frontend/usage_quotas/components/limited_access_modal_spec.js
@@ -0,0 +1,47 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import LimitedAccessModal from 'ee/usage_quotas/components/limited_access_modal.vue';
+
+describe('LimitedAccessModal', () => {
+  let wrapper;
+
+  const createComponent = (props = {}) => {
+    wrapper = shallowMountExtended(LimitedAccessModal, {
+      propsData: { ...props },
+    });
+  };
+  const findModal = () => wrapper.findComponent(GlModal);
+
+  it('has correct button', () => {
+    createComponent();
+
+    expect(findModal().props('actionPrimary')).toStrictEqual({
+      text: 'Close',
+      attributes: { variant: 'confirm' },
+    });
+  });
+
+  describe('with reseller', () => {
+    beforeEach(() => {
+      createComponent({ limitedAccessReason: 'MANAGED_BY_RESELLER' });
+    });
+
+    it('shows correct content', () => {
+      const modal = findModal();
+
+      expect(modal.text()).toContain('GitLab Partner');
+    });
+  });
+
+  describe('with ramp', () => {
+    beforeEach(() => {
+      createComponent({ limitedAccessReason: 'RAMP_SUBSCRIPTION' });
+    });
+
+    it('shows correct content', () => {
+      const modal = findModal();
+
+      expect(modal.text()).toContain('GitLab sales representative');
+    });
+  });
+});
diff --git a/ee/spec/frontend/usage_quotas/components/statistics_seats_card_spec.js b/ee/spec/frontend/usage_quotas/components/statistics_seats_card_spec.js
index 96d565e36a7f5700dd7edeb61bed8bbb046049ab..79bee19ed0198784d8657f4e516a58daf2b61252 100644
--- a/ee/spec/frontend/usage_quotas/components/statistics_seats_card_spec.js
+++ b/ee/spec/frontend/usage_quotas/components/statistics_seats_card_spec.js
@@ -1,7 +1,15 @@
 import { GlLink } from '@gitlab/ui';
+import { nextTick } from 'vue';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import StatisticsSeatsCard from 'ee/usage_quotas/seats/components/statistics_seats_card.vue';
 import Tracking from '~/tracking';
+import { visitUrl } from '~/lib/utils/url_utility';
+import LimitedAccessModal from 'ee/usage_quotas/components/limited_access_modal.vue';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+  ...jest.requireActual('~/lib/utils/url_utility'),
+  visitUrl: jest.fn().mockName('visitUrlMock'),
+}));
 
 describe('StatisticsSeatsCard', () => {
   let wrapper;
@@ -15,12 +23,16 @@ describe('StatisticsSeatsCard', () => {
   const createComponent = (props = {}) => {
     wrapper = shallowMountExtended(StatisticsSeatsCard, {
       propsData: { ...defaultProps, ...props },
+      stubs: {
+        LimitedAccessModal,
+      },
     });
   };
 
   const findSeatsUsedBlock = () => wrapper.findByTestId('seats-used-block');
   const findSeatsOwedBlock = () => wrapper.findByTestId('seats-owed-block');
   const findPurchaseButton = () => wrapper.findByTestId('purchase-button');
+  const findLimitedAccessModal = () => wrapper.findComponent(LimitedAccessModal);
 
   describe('seats used block', () => {
     it('renders seats used block if seatsUsed is passed', () => {
@@ -65,8 +77,6 @@ describe('StatisticsSeatsCard', () => {
       const purchaseButton = findPurchaseButton();
 
       expect(purchaseButton.exists()).toBe(true);
-      expect(purchaseButton.attributes('href')).toBe(purchaseButtonLink);
-      expect(purchaseButton.attributes('target')).toBe('_blank');
     });
 
     it('does not render purchase button if purchase link is not passed', () => {
@@ -85,5 +95,54 @@ describe('StatisticsSeatsCard', () => {
         property: 'usage_quotas_page',
       });
     });
+
+    it('redirects when clicked', () => {
+      createComponent();
+      findPurchaseButton().vm.$emit('click');
+
+      expect(visitUrl).toHaveBeenCalledWith('https://gitlab.com/purchase-more-seats');
+    });
+  });
+
+  describe('limited access modal', () => {
+    afterEach(() => {
+      jest.restoreAllMocks();
+    });
+
+    describe('when limitedAccessModal FF is on', () => {
+      beforeEach(async () => {
+        gon.features = { limitedAccessModal: true };
+        createComponent();
+
+        findPurchaseButton().vm.$emit('click');
+        await nextTick();
+      });
+
+      it('shows modal', () => {
+        expect(findLimitedAccessModal().isVisible()).toBe(true);
+      });
+
+      it('does not navigate to URL', () => {
+        expect(visitUrl).not.toHaveBeenCalled();
+      });
+    });
+
+    describe('when limitedAccessModal FF is off', () => {
+      beforeEach(async () => {
+        gon.features = { limitedAccessModal: false };
+        createComponent();
+
+        findPurchaseButton().vm.$emit('click');
+        await nextTick();
+      });
+
+      it('does not show modal', () => {
+        expect(findLimitedAccessModal().exists()).toBe(false);
+      });
+
+      it('navigates to URL', () => {
+        expect(visitUrl).toHaveBeenCalledWith('https://gitlab.com/purchase-more-seats');
+      });
+    });
   });
 });
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6303647a4d26136c50d1d80eeac72a0d154e2051..7ca4705cf810890229c8c077f2212158d9081a98 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -45791,6 +45791,18 @@ msgstr ""
 msgid "SubscriptionBanner|Upload new license"
 msgstr ""
 
+msgid "SubscriptionMangement|If you'd like to add more seats, upgrade your plan, or purchase additional products, contact your GitLab sales representative."
+msgstr ""
+
+msgid "SubscriptionMangement|This is a custom subscription managed by the GitLab Sales team"
+msgstr ""
+
+msgid "SubscriptionMangement|To make changes to a read-only subscription or purchase additional products, contact your GitLab Partner."
+msgstr ""
+
+msgid "SubscriptionMangement|Your subscription is in read-only mode"
+msgstr ""
+
 msgid "SubscriptionTable|Add seats"
 msgstr ""