diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 6ce8f75f198b227c5cbc72e9882f5f5fb8ff9a44..7e1c3c5c78ea99367bbd17175f966c19af7f4819 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -191,6 +191,7 @@
     "WorkItemWidgetColor",
     "WorkItemWidgetCurrentUserTodos",
     "WorkItemWidgetDescription",
+    "WorkItemWidgetDesigns",
     "WorkItemWidgetHealthStatus",
     "WorkItemWidgetHierarchy",
     "WorkItemWidgetIteration",
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
index fda22db77795d8a4e9a0d1eff154779b5dde2e5d..d53f074b27726bfc988afdf2354d8c77cdaa47ba 100644
--- a/app/graphql/types/work_items/widget_interface.rb
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -24,7 +24,8 @@ module WidgetInterface
         ::Types::WorkItems::Widgets::AwardEmojiType,
         ::Types::WorkItems::Widgets::LinkedItemsType,
         ::Types::WorkItems::Widgets::ParticipantsType,
-        ::Types::WorkItems::Widgets::TimeTrackingType
+        ::Types::WorkItems::Widgets::TimeTrackingType,
+        ::Types::WorkItems::Widgets::DesignsType
       ].freeze
 
       def self.ce_orphan_types
@@ -64,6 +65,8 @@ def self.resolve_type(object, context)
           ::Types::WorkItems::Widgets::ParticipantsType
         when ::WorkItems::Widgets::TimeTracking
           ::Types::WorkItems::Widgets::TimeTrackingType
+        when ::WorkItems::Widgets::Designs
+          ::Types::WorkItems::Widgets::DesignsType
         else
           raise "Unknown GraphQL type for widget #{object}"
         end
diff --git a/app/graphql/types/work_items/widgets/designs_type.rb b/app/graphql/types/work_items/widgets/designs_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c4cf37c9cb7cef3bdadd4ee9a45874e63bca202a
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/designs_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+  module WorkItems
+    module Widgets
+      # Disabling widget level authorization as it might be too granular
+      # and we already authorize the parent work item
+      # rubocop:disable Graphql/AuthorizeTypes -- reason above
+      class DesignsType < BaseObject
+        graphql_name 'WorkItemWidgetDesigns'
+        description 'Represents designs widget'
+
+        implements Types::WorkItems::WidgetInterface
+
+        field :design_collection, Types::DesignManagement::DesignCollectionType, null: true,
+          description: 'Collection of design images associated with the issue.'
+      end
+      # rubocop:enable Graphql/AuthorizeTypes
+    end
+  end
+end
diff --git a/app/models/work_items/widget_definition.rb b/app/models/work_items/widget_definition.rb
index a8f35bec02966279222ec3767bed434ee4635642..6f2f16d9846173adfe671064d32dd8ccb6f62fd6 100644
--- a/app/models/work_items/widget_definition.rb
+++ b/app/models/work_items/widget_definition.rb
@@ -36,7 +36,8 @@ class WidgetDefinition < ApplicationRecord
       color: 18, # EE-only
       rolledup_dates: 19, # EE-only
       participants: 20,
-      time_tracking: 21
+      time_tracking: 21,
+      designs: 22
     }
 
     def self.available_widgets
diff --git a/app/models/work_items/widgets/designs.rb b/app/models/work_items/widgets/designs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cfb11c999239b67c951647e0f1303732d94df319
--- /dev/null
+++ b/app/models/work_items/widgets/designs.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module WorkItems
+  module Widgets
+    class Designs < Base
+      delegate :designs, :design_versions, :design_collection, to: :work_item
+    end
+  end
+end
diff --git a/db/post_migrate/20240130070854_add_designs_widget_to_work_item_definitions.rb b/db/post_migrate/20240130070854_add_designs_widget_to_work_item_definitions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1cd8dbffc620eda7e4f9165e379a4c7527c77e3d
--- /dev/null
+++ b/db/post_migrate/20240130070854_add_designs_widget_to_work_item_definitions.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class AddDesignsWidgetToWorkItemDefinitions < Gitlab::Database::Migration[2.2]
+  milestone '16.9'
+
+  class WorkItemType < MigrationRecord
+    self.table_name = 'work_item_types'
+  end
+
+  class WidgetDefinition < MigrationRecord
+    self.table_name = 'work_item_widget_definitions'
+  end
+
+  restrict_gitlab_migration gitlab_schema: :gitlab_main
+  disable_ddl_transaction!
+
+  WIDGET_NAME = 'Designs'
+  WIDGET_ENUM_VALUE = 22
+  WORK_ITEM_TYPE = 'Issue'
+
+  def up
+    type = WorkItemType.find_by_name_and_namespace_id(WORK_ITEM_TYPE, nil)
+
+    unless type
+      Gitlab::AppLogger.warn("type #{WORK_ITEM_TYPE} is missing, not adding widget")
+      return
+    end
+
+    widget = {
+      work_item_type_id: type.id,
+      name: WIDGET_NAME,
+      widget_type: WIDGET_ENUM_VALUE
+    }
+
+    WidgetDefinition.upsert_all(
+      [widget],
+      unique_by: :index_work_item_widget_definitions_on_default_witype_and_name
+    )
+  end
+
+  def down
+    WidgetDefinition.where(name: WIDGET_NAME).delete_all
+  end
+end
diff --git a/db/schema_migrations/20240130070854 b/db/schema_migrations/20240130070854
new file mode 100644
index 0000000000000000000000000000000000000000..511e6479813948961b084acf7da36af5b85ee75e
--- /dev/null
+++ b/db/schema_migrations/20240130070854
@@ -0,0 +1 @@
+2ae6840adaf8ce18391c82bf27fdefba53b0b994fe356549430fe90878a27fb0
\ No newline at end of file
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 7a5cc74479a4772a8a96fa57866f957c024fe900..b70f6116c0513827fd98e5d4dc8eb125b92e4dde 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -29445,6 +29445,17 @@ Represents a description widget.
 | <a id="workitemwidgetdescriptionlasteditedby"></a>`lastEditedBy` | [`UserCore`](#usercore) | User that made the last edit to the work item's description. |
 | <a id="workitemwidgetdescriptiontype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
 
+### `WorkItemWidgetDesigns`
+
+Represents designs widget.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="workitemwidgetdesignsdesigncollection"></a>`designCollection` | [`DesignCollection`](#designcollection) | Collection of design images associated with the issue. |
+| <a id="workitemwidgetdesignstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
+
 ### `WorkItemWidgetHealthStatus`
 
 Represents a health status widget.
@@ -32616,6 +32627,7 @@ Type of a work item widget.
 | <a id="workitemwidgettypecolor"></a>`COLOR` | Color widget. |
 | <a id="workitemwidgettypecurrent_user_todos"></a>`CURRENT_USER_TODOS` | Current User Todos widget. |
 | <a id="workitemwidgettypedescription"></a>`DESCRIPTION` | Description widget. |
+| <a id="workitemwidgettypedesigns"></a>`DESIGNS` | Designs widget. |
 | <a id="workitemwidgettypehealth_status"></a>`HEALTH_STATUS` | Health Status widget. |
 | <a id="workitemwidgettypehierarchy"></a>`HIERARCHY` | Hierarchy widget. |
 | <a id="workitemwidgettypeiteration"></a>`ITERATION` | Iteration widget. |
@@ -34312,6 +34324,7 @@ Implementations:
 - [`WorkItemWidgetColor`](#workitemwidgetcolor)
 - [`WorkItemWidgetCurrentUserTodos`](#workitemwidgetcurrentusertodos)
 - [`WorkItemWidgetDescription`](#workitemwidgetdescription)
+- [`WorkItemWidgetDesigns`](#workitemwidgetdesigns)
 - [`WorkItemWidgetHealthStatus`](#workitemwidgethealthstatus)
 - [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
 - [`WorkItemWidgetIteration`](#workitemwidgetiteration)
diff --git a/ee/spec/models/ee/work_items/widget_definition_spec.rb b/ee/spec/models/ee/work_items/widget_definition_spec.rb
index 48bbf059f021edebc912b5cae46f1bc074088b2c..992ce97222b25eeb320a2c6412087519aaa995a7 100644
--- a/ee/spec/models/ee/work_items/widget_definition_spec.rb
+++ b/ee/spec/models/ee/work_items/widget_definition_spec.rb
@@ -29,7 +29,8 @@
         ::WorkItems::Widgets::Color,
         ::WorkItems::Widgets::RolledupDates,
         ::WorkItems::Widgets::Participants,
-        ::WorkItems::Widgets::TimeTracking
+        ::WorkItems::Widgets::TimeTracking,
+        ::WorkItems::Widgets::Designs
       )
     end
   end
diff --git a/lib/gitlab/database_importers/work_items/base_type_importer.rb b/lib/gitlab/database_importers/work_items/base_type_importer.rb
index 3cf5072eb6c513ff01bf741ecf716eca20e3ab3c..0dba5d013d028fec70c3dd2dca6dcef81c2274c3 100644
--- a/lib/gitlab/database_importers/work_items/base_type_importer.rb
+++ b/lib/gitlab/database_importers/work_items/base_type_importer.rb
@@ -26,7 +26,8 @@ module BaseTypeImporter
           color: 'Color',
           rolledup_dates: 'Rolledup dates',
           participants: 'Participants',
-          time_tracking: 'Time tracking'
+          time_tracking: 'Time tracking',
+          designs: 'Designs'
         }.freeze
 
         WIDGETS_FOR_TYPE = {
@@ -46,7 +47,8 @@ module BaseTypeImporter
             :award_emoji,
             :linked_items,
             :participants,
-            :time_tracking
+            :time_tracking,
+            :designs
           ],
           incident: [
             :assignees,
diff --git a/spec/graphql/types/work_items/widget_interface_spec.rb b/spec/graphql/types/work_items/widget_interface_spec.rb
index df871dbe9aa969776136c32b9d7642558135f9f3..6e66f335d89ff8e4eeef15f7565c7df918782983 100644
--- a/spec/graphql/types/work_items/widget_interface_spec.rb
+++ b/spec/graphql/types/work_items/widget_interface_spec.rb
@@ -29,6 +29,7 @@
       WorkItems::Widgets::Milestone        | Types::WorkItems::Widgets::MilestoneType
       WorkItems::Widgets::Participants     | Types::WorkItems::Widgets::ParticipantsType
       WorkItems::Widgets::TimeTracking     | Types::WorkItems::Widgets::TimeTrackingType
+      WorkItems::Widgets::Designs          | Types::WorkItems::Widgets::DesignsType
     end
 
     with_them do
diff --git a/spec/graphql/types/work_items/widgets/designs_type_spec.rb b/spec/graphql/types/work_items/widgets/designs_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26ae35f3577a1f366c9feb688a9cde4025ddfbcf
--- /dev/null
+++ b/spec/graphql/types/work_items/widgets/designs_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::Widgets::DesignsType, feature_category: :team_planning do
+  it 'exposes the expected fields' do
+    expected_fields = %i[type design_collection]
+
+    expect(described_class).to have_graphql_fields(*expected_fields)
+  end
+end
diff --git a/spec/models/work_items/widget_definition_spec.rb b/spec/models/work_items/widget_definition_spec.rb
index 6751d331423b599fcfad425d6230f00dff5c2765..d25b5cb727951416ada88aa8aeddbc5fe99cdb48 100644
--- a/spec/models/work_items/widget_definition_spec.rb
+++ b/spec/models/work_items/widget_definition_spec.rb
@@ -17,7 +17,8 @@
       ::WorkItems::Widgets::AwardEmoji,
       ::WorkItems::Widgets::LinkedItems,
       ::WorkItems::Widgets::Participants,
-      ::WorkItems::Widgets::TimeTracking
+      ::WorkItems::Widgets::TimeTracking,
+      ::WorkItems::Widgets::Designs
     ]
 
     if Gitlab.ee?
diff --git a/spec/models/work_items/widgets/designs_spec.rb b/spec/models/work_items/widgets/designs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..003f9f1d5173220e692e499e93a85981440591f6
--- /dev/null
+++ b/spec/models/work_items/widgets/designs_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::Widgets::Designs, feature_category: :team_planning do
+  let_it_be(:work_item) { create(:work_item) }
+
+  describe '.type' do
+    specify { expect(described_class.type).to eq(:designs) }
+  end
+
+  describe '#type' do
+    specify { expect(described_class.new(work_item).type).to eq(:designs) }
+  end
+
+  describe '#designs' do
+    it 'returns all designs' do
+      create_list(:design, 3, :with_file, issue: work_item)
+      design_a = create(:design, :with_file, issue: work_item)
+
+      expect(described_class.new(work_item).designs.count).to eq(4)
+
+      expect(described_class.new(work_item).designs).to include(design_a)
+    end
+  end
+
+  describe '#design_versions' do
+    it 'returns all design versions' do
+      create_list(:design_version, 2, issue: work_item)
+      last_version = create(:design_version, issue: work_item)
+
+      expect(described_class.new(work_item).design_versions.count).to eq(3)
+      expect(described_class.new(work_item).design_versions).to include(last_version)
+    end
+  end
+
+  describe '#design_collection' do
+    it 'returns a design collection' do
+      collection = described_class.new(work_item).design_collection
+
+      expect(collection).to be_a(DesignManagement::DesignCollection)
+      expect(collection.issue).to eq(work_item)
+    end
+  end
+end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 1879eb60261ddb2807a2b787b01064dc7bcdccd5..45f79de8e5afbdf5823dfccd1c37e18124cbee17 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -830,6 +830,267 @@ def pagination_results_data(nodes)
       end
     end
 
+    describe 'designs widget' do
+      include DesignManagementTestHelpers
+
+      let(:work_item_fields) do
+        query_graphql_field(
+          :widgets, {}, query_graphql_field(
+            'type ... on WorkItemWidgetDesigns', {}, query_graphql_field(
+              :design_collection, nil, design_collection_fields
+            )
+          )
+        )
+      end
+
+      let(:design_collection_fields) { nil }
+
+      let(:post_query) { post_graphql(query, current_user: current_user) }
+
+      let(:design_collection_data) { work_item_data['widgets'].find { |w| w['type'] == 'DESIGNS' }['designCollection'] }
+
+      before do
+        project.add_developer(developer)
+        enable_design_management
+      end
+
+      def id_hash(object)
+        a_graphql_entity_for(object)
+      end
+
+      shared_examples 'fetch a design-like object by ID' do
+        let(:design) { design_a }
+
+        let(:design_fields) do
+          [
+            :filename,
+            query_graphql_field(:project, :id)
+          ]
+        end
+
+        let(:design_collection_fields) do
+          query_graphql_field(object_field_name, object_params, object_fields)
+        end
+
+        let(:object_fields) { design_fields }
+
+        context 'when the ID is passed' do
+          let(:object_params) { { id: global_id_of(object) } }
+          let(:result_fields) { {} }
+
+          let(:expected_fields) do
+            result_fields.merge({ 'filename' => design.filename, 'project' => id_hash(project) })
+          end
+
+          it 'retrieves the object' do
+            post_query
+            data = design_collection_data[GraphqlHelpers.fieldnamerize(object_field_name)]
+
+            expect(data).to match(a_hash_including(expected_fields))
+          end
+
+          context 'when the user is unauthorized' do
+            let(:current_user) { create(:user) }
+
+            it_behaves_like 'a failure to find anything'
+          end
+
+          context 'without parameters' do
+            let(:object_params) { nil }
+
+            it 'raises an error' do
+              post_query
+
+              expect(graphql_errors).to include(no_argument_error)
+            end
+          end
+        end
+
+        context 'when attempting to retrieve an object from a different issue' do
+          let(:object_params) { { id: global_id_of(object_on_other_issue) } }
+
+          it_behaves_like 'a failure to find anything'
+        end
+      end
+
+      context 'when work item is an issue' do
+        let_it_be(:issue_work_item) { create(:work_item, :issue, project: project) }
+        let_it_be(:issue_work_item1) { create(:work_item, :issue, project: project) }
+        let_it_be(:design_a) { create(:design, issue: issue_work_item) }
+        let_it_be(:version_a) { create(:design_version, issue: issue_work_item, created_designs: [design_a]) }
+        let_it_be(:global_id) { issue_work_item.to_gid.to_s }
+
+        describe '.designs' do
+          let(:design_collection_fields) do
+            query_graphql_field('designs', {}, "nodes { id event filename }")
+          end
+
+          it 'returns design data' do
+            post_query
+
+            expect(design_collection_data).to include(
+              'designs' => include(
+                'nodes' => include(
+                  hash_including(
+                    'id' => design_a.to_gid.to_s,
+                    'event' => 'CREATION',
+                    'filename' => design_a.filename
+                  )
+                )
+              )
+            )
+          end
+        end
+
+        describe 'copy_state' do
+          let(:design_collection_fields) do
+            'copyState'
+          end
+
+          it 'returns copyState of designCollection' do
+            post_query
+
+            expect(design_collection_data).to include(
+              'copyState' => 'READY'
+            )
+          end
+        end
+
+        describe '.versions' do
+          let(:design_collection_fields) do
+            query_graphql_field('versions', {}, "nodes { id sha createdAt }")
+          end
+
+          it 'returns versions data' do
+            post_query
+
+            expect(design_collection_data).to include(
+              'versions' => include(
+                'nodes' => include(
+                  hash_including(
+                    'id' => version_a.to_gid.to_s,
+                    'sha' => version_a.sha,
+                    'createdAt' => version_a.created_at.iso8601
+                  )
+                )
+              )
+            )
+          end
+        end
+
+        describe '.version' do
+          let(:version) { version_a }
+
+          let(:design_collection_fields) do
+            query_graphql_field(:version, version_params, 'id sha')
+          end
+
+          context 'with no parameters' do
+            let(:version_params) { nil }
+
+            it 'raises an error' do
+              post_query
+
+              expect(graphql_errors).to include(a_hash_including("message" => "one of id or sha is required"))
+            end
+          end
+
+          shared_examples 'a successful query for a version' do
+            it 'finds the version' do
+              post_query
+
+              data = design_collection_data['version']
+
+              expect(data).to match a_graphql_entity_for(version, :sha)
+            end
+          end
+
+          context 'with (sha: STRING_TYPE)' do
+            let(:version_params) { { sha: version.sha } }
+
+            it_behaves_like 'a successful query for a version'
+          end
+
+          context 'with (id: ID_TYPE)' do
+            let(:version_params) { { id: global_id_of(version) } }
+
+            it_behaves_like 'a successful query for a version'
+          end
+        end
+
+        describe '.design' do
+          it_behaves_like 'fetch a design-like object by ID' do
+            let(:object) { design }
+            let(:object_field_name) { :design }
+
+            let(:no_argument_error) do
+              a_hash_including("message" => "one of id or filename must be passed")
+            end
+
+            let_it_be(:object_on_other_issue) { create(:design, issue: issue_work_item1) }
+          end
+        end
+
+        describe '.designAtVersion' do
+          it_behaves_like 'fetch a design-like object by ID' do
+            let(:object) { build(:design_at_version, design: design, version: version) }
+            let(:object_field_name) { :design_at_version }
+
+            let(:version) { version_a }
+
+            let(:result_fields) { { 'version' => id_hash(version) } }
+            let(:object_fields) do
+              design_fields + [query_graphql_field(:version, :id)]
+            end
+
+            let(:no_argument_error) do
+              a_hash_including("message" => "Field 'designAtVersion' is missing required arguments: id")
+            end
+
+            let(:object_on_other_issue) { build(:design_at_version, issue: issue_work_item1) }
+          end
+        end
+
+        describe 'N+1 query check' do
+          let(:design_collection_fields) do
+            query_graphql_field('designs', {}, "nodes { id event filename}")
+          end
+
+          it 'avoids N+1 queries', :use_sql_query_cache do
+            post_query # warmup
+            control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+              post_query
+            end
+
+            create_list(:work_item, 3, namespace: group) do |item|
+              create(:design, :with_file, issue: item)
+            end
+
+            expect do
+              post_query
+            end.to issue_same_number_of_queries_as(control_count)
+            expect_graphql_errors_to_be_empty
+          end
+        end
+      end
+
+      context 'when work item base type is non issue' do
+        let_it_be(:epic) { create(:work_item, :epic, namespace: group) }
+        let_it_be(:global_id) { epic.to_gid.to_s }
+
+        it 'returns without design' do
+          post_query
+
+          expect(epic&.work_item_type&.base_type).not_to match('issue')
+          expect(work_item_data['widgets']).not_to include(
+            hash_including(
+              'type' => 'DESIGNS'
+            )
+          )
+        end
+      end
+    end
+
     context 'when an Issue Global ID is provided' do
       let(:global_id) { Issue.find(work_item.id).to_gid.to_s }