diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7b9efa29cb0238b4007bf897f7604efda8ff286b
--- /dev/null
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue
@@ -0,0 +1,204 @@
+<script>
+import {
+  GlAvatarLabeled,
+  GlButton,
+  GlLink,
+  GlTab,
+  GlTabs,
+  GlTableLite,
+  GlTooltipDirective,
+} from '@gitlab/ui';
+import { isEmpty, maxBy, range } from 'lodash';
+import { __, s__, sprintf } from '~/locale';
+
+export default {
+  name: 'CandidateDetail',
+  components: {
+    GlAvatarLabeled,
+    GlButton,
+    GlLink,
+    GlTab,
+    GlTabs,
+    GlTableLite,
+  },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
+  props: {
+    candidate: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    info() {
+      return this.candidate.info;
+    },
+    ciJob() {
+      return this.info.ciJob;
+    },
+    hasMetadata() {
+      return !isEmpty(this.candidate.metadata);
+    },
+    hasParameters() {
+      return !isEmpty(this.candidate.params);
+    },
+    hasMetrics() {
+      return !isEmpty(this.candidate.metrics);
+    },
+    metricsTableFields() {
+      const maxStep = maxBy(this.candidate.metrics, 'step').step;
+      const rowClass = '!gl-p-3';
+
+      const cssClasses = { thClass: rowClass, tdClass: rowClass };
+
+      const fields = range(maxStep + 1).map((step) => ({
+        key: step.toString(),
+        label: sprintf(s__('MlModelRegistry|Step %{step}'), { step }),
+        ...cssClasses,
+      }));
+
+      return [{ key: 'name', label: s__('MlModelRegistry|Metric'), ...cssClasses }, ...fields];
+    },
+    metricsTableItems() {
+      const items = {};
+      this.candidate.metrics.forEach((metric) => {
+        const metricRow = items[metric.name] || { name: metric.name };
+        metricRow[metric.step] = metric.value;
+        items[metric.name] = metricRow;
+      });
+
+      return Object.values(items);
+    },
+    parameterTableItems() {
+      return this.candidate.params.map((param) => ({ name: param.name, value: param.value }));
+    },
+    parameterTableFields() {
+      return [
+        { key: 'name', label: __('Name') },
+        { key: 'value', label: __('Value') },
+      ];
+    },
+  },
+  methods: {
+    copyMlflowId() {
+      navigator.clipboard.writeText(this.info.eid);
+    },
+  },
+  i18n: {
+    detailsLabel: s__('MlModelRegistry|Details & Metadata'),
+    artifactsLabel: s__('MlModelRegistry|Artifacts'),
+    mlflowIdLabel: s__('MlModelRegistry|MLflow run ID'),
+    ciSectionLabel: s__('MlModelRegistry|CI Info'),
+    jobLabel: __('Job'),
+    ciUserLabel: s__('MlModelRegistry|Triggered by'),
+    ciMrLabel: __('Merge request'),
+    parametersLabel: s__('MlModelRegistry|Parameters'),
+    performanceLabel: s__('MlModelRegistry|Performance'),
+    noParametersMessage: s__('MlModelRegistry|No logged parameters'),
+    noMetricsMessage: s__('MlModelRegistry|No logged metrics'),
+    noMetadataMessage: s__('MlModelRegistry|No logged metadata'),
+    noCiMessage: s__('MlModelRegistry|Candidate not linked to a CI build'),
+    noArtifactsMessage: s__('MlModelRegistry|No logged artifacts.'),
+    copyMessage: __('Copy MLflow run ID'),
+  },
+};
+</script>
+
+<template>
+  <div>
+    <gl-tabs class="mt-5">
+      <gl-tab :title="$options.i18n.detailsLabel" class="gl-pt-3" data-testid="details">
+        <section>
+          <label class="gl-font-bold">{{ $options.i18n.mlflowIdLabel }}</label>
+          <div
+            class="gl-m-0 gl-w-fit gl-border-2 gl-border-solid gl-border-default gl-px-2 gl-py-0"
+          >
+            <span data-testid="mlflow-run-id">
+              {{ info.eid }}
+            </span>
+            <gl-button
+              v-gl-tooltip
+              variant="default"
+              category="tertiary"
+              size="medium"
+              :aria-label="$options.i18n.copyMessage"
+              :title="$options.i18n.copyMessage"
+              icon="copy-to-clipboard"
+              @click="copyMlflowId"
+            />
+          </div>
+          <div
+            v-for="item in candidate.metadata"
+            :key="item.name"
+            class="mt-3"
+            data-testid="metadata"
+          >
+            <h5 class="gl-font-bold">{{ item.name }}</h5>
+            <p>{{ item.value }}</p>
+          </div>
+        </section>
+        <section class="gl-pt-3" data-testid="parameters">
+          <h4>{{ $options.i18n.parametersLabel }}</h4>
+          <gl-table-lite
+            v-if="hasParameters"
+            :items="parameterTableItems"
+            :fields="parameterTableFields"
+            class="gl-w-100"
+            hover
+            data-testid="parameters-table"
+          />
+          <div v-else class="gl-text-subtle">{{ $options.i18n.noParametersMessage }}</div>
+        </section>
+        <section data-testid="ci">
+          <h4>{{ $options.i18n.ciSectionLabel }}</h4>
+          <div v-if="ciJob" class="pt-3">
+            <div>
+              <h5 class="gl-font-bold">{{ $options.i18n.jobLabel }}</h5>
+              <gl-link :href="ciJob.path" data-testid="ci-job-path">
+                {{ ciJob.name }}
+              </gl-link>
+            </div>
+            <div v-if="ciJob.user" class="pt-3">
+              <h5 class="gl-font-bold">{{ $options.i18n.ciUserLabel }}</h5>
+              <gl-avatar-labeled label="" :size="24" :src="ciJob.user.avatar">
+                <gl-link :href="ciJob.user.path">
+                  {{ ciJob.user.name }}
+                </gl-link>
+              </gl-avatar-labeled>
+            </div>
+            <div v-if="ciJob.mergeRequest" class="pt-3">
+              <h5 class="gl-font-bold">{{ $options.i18n.ciMrLabel }}</h5>
+              <gl-link :href="ciJob.mergeRequest.path">
+                !{{ ciJob.mergeRequest.iid }} {{ ciJob.mergeRequest.title }}
+              </gl-link>
+            </div>
+          </div>
+          <div v-else class="gl-text-subtle">{{ $options.i18n.noCiMessage }}</div>
+        </section>
+      </gl-tab>
+      <gl-tab :title="$options.i18n.artifactsLabel" class="pt-3" data-testid="artifacts">
+        <gl-link
+          v-if="info.pathToArtifact"
+          :href="info.pathToArtifact"
+          data-testid="artifacts-link"
+        >
+          {{ $options.i18n.artifactsLabel }}
+        </gl-link>
+        <div v-else class="gl-text-subtle">{{ $options.i18n.noArtifactsMessage }}</div>
+      </gl-tab>
+      <gl-tab :title="$options.i18n.performanceLabel" class="pt-3" data-testid="metrics">
+        <div v-if="hasMetrics" class="gl-overflow-x-auto">
+          <gl-table-lite
+            :items="metricsTableItems"
+            :fields="metricsTableFields"
+            class="gl-w-100"
+            hover
+            data-testid="metrics-table"
+          />
+        </div>
+        <div v-else class="gl-text-subtle">{{ $options.i18n.noMetricsMessage }}</div>
+      </gl-tab>
+    </gl-tabs>
+  </div>
+</template>
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_header.vue b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_header.vue
new file mode 100644
index 0000000000000000000000000000000000000000..947fba5f5be36a42453395618e8ad898225132d3
--- /dev/null
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/candidate_header.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlBadge, GlIcon, GlLink } from '@gitlab/ui';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import PageHeading from '~/vue_shared/components/page_heading.vue';
+import { s__, sprintf } from '~/locale';
+import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
+
+export default {
+  name: 'CandidateHeader',
+  components: {
+    DeleteButton,
+    GlBadge,
+    GlIcon,
+    GlLink,
+    PageHeading,
+    TimeAgoTooltip,
+  },
+  mixins: [timeagoMixin],
+  props: {
+    info: {
+      type: Object,
+      required: true,
+    },
+  },
+  computed: {
+    title() {
+      return sprintf(s__('MlExperimentTracking|Candidate %{id}'), { id: this.info.iid });
+    },
+    authorInfo() {
+      return sprintf(s__('MlExperimentTracking|by %{author}'), { author: this.info.authorName });
+    },
+    statusVariant() {
+      return this.$options.statusVariants[this.info.status];
+    },
+  },
+  i18n: {
+    deleteCandidateConfirmationMessage: s__(
+      'MlExperimentTracking|Deleting this candidate will delete the associated parameters, metrics, and metadata.',
+    ),
+    deleteCandidatePrimaryActionLabel: s__('MlExperimentTracking|Delete candidate'),
+    deleteCandidateModalTitle: s__('MlExperimentTracking|Delete candidate?'),
+  },
+  statusVariants: {
+    running: 'success',
+    scheduled: 'info',
+    finished: 'muted',
+    failed: 'warning',
+    killed: 'danger',
+  },
+};
+</script>
+
+<template>
+  <div class="gl-flex gl-items-center gl-justify-between">
+    <page-heading>
+      <template #heading>
+        <gl-link data-testid="experiment-link" :href="info.pathToExperiment">
+          {{ info.experimentName }} /
+        </gl-link>
+        <span class="gl-inline-flex gl-items-center">
+          {{ title }}
+        </span>
+      </template>
+      <template #description>
+        <div class="gl-flex gl-flex-wrap gl-items-center gl-gap-x-2">
+          <gl-badge :variant="statusVariant">
+            <gl-icon name="issue-type-test-case" />
+            {{ info.status }}
+          </gl-badge>
+          <time-ago-tooltip :time="info.createdAt" />
+          <gl-link
+            v-if="info.authorName"
+            data-testid="author-link"
+            class="js-user-link gl-font-bold !gl-text-subtle"
+            :href="info.authorWebUrl"
+          >
+            <span class="sm:gl-inline">{{ authorInfo }}</span>
+          </gl-link>
+        </div>
+      </template>
+    </page-heading>
+
+    <delete-button
+      :delete-path="info.path"
+      :delete-confirmation-text="$options.i18n.deleteCandidateConfirmationMessage"
+      :action-primary-text="$options.i18n.deleteCandidatePrimaryActionLabel"
+      :modal-title="$options.i18n.deleteCandidateModalTitle"
+    />
+  </div>
+</template>
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue
index fdd7cad6d6581315608487eec0dcf18960c5e11c..75198327de122b289445e983357323972be21dce 100644
--- a/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/candidates/show/ml_candidates_show.vue
@@ -1,14 +1,11 @@
 <script>
-import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue';
-import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
-import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
-import { s__ } from '~/locale';
+import CandidateHeader from './candidate_header.vue';
+import CandidateDetail from './candidate_detail.vue';
 
 export default {
   name: 'MlCandidatesShow',
   components: {
-    ModelExperimentsHeader,
-    DeleteButton,
+    CandidateHeader,
     CandidateDetail,
   },
   props: {
@@ -22,28 +19,12 @@ export default {
       return Object.freeze(this.candidate.info);
     },
   },
-  i18n: {
-    TITLE_LABEL: s__('MlExperimentTracking|Model candidate details'),
-    DELETE_CANDIDATE_CONFIRMATION_MESSAGE: s__(
-      'MlExperimentTracking|Deleting this candidate will delete the associated parameters, metrics, and metadata.',
-    ),
-    DELETE_CANDIDATE_PRIMARY_ACTION_LABEL: s__('MlExperimentTracking|Delete candidate'),
-    DELETE_CANDIDATE_MODAL_TITLE: s__('MlExperimentTracking|Delete candidate?'),
-  },
 };
 </script>
 
 <template>
   <div>
-    <model-experiments-header :page-title="$options.i18n.TITLE_LABEL" hide-mlflow-usage>
-      <delete-button
-        :delete-path="info.path"
-        :delete-confirmation-text="$options.i18n.DELETE_CANDIDATE_CONFIRMATION_MESSAGE"
-        :action-primary-text="$options.i18n.DELETE_CANDIDATE_PRIMARY_ACTION_LABEL"
-        :modal-title="$options.i18n.DELETE_CANDIDATE_MODAL_TITLE"
-      />
-    </model-experiments-header>
-
+    <candidate-header :info="info" />
     <candidate-detail :candidate="candidate" />
   </div>
 </template>
diff --git a/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue b/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue
index c61b75ae931b7c28f7f65859f71d5ea86a0ce469..83999bb5287a77dc32bb8b1cf9beccc083b79002 100644
--- a/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue
+++ b/app/assets/javascripts/ml/model_registry/components/candidate_detail.vue
@@ -37,11 +37,6 @@ export default {
       type: Object,
       required: true,
     },
-    showInfoSection: {
-      type: Boolean,
-      required: false,
-      default: true,
-    },
   },
   i18n: {
     INFO_LABEL,
@@ -108,32 +103,6 @@ export default {
 
 <template>
   <div>
-    <section v-if="showInfoSection" class="gl-mb-6">
-      <table class="candidate-details">
-        <tbody>
-          <detail-row :label="$options.i18n.ID_LABEL">
-            {{ info.iid }}
-          </detail-row>
-
-          <detail-row :label="$options.i18n.MLFLOW_ID_LABEL">{{ info.eid }}</detail-row>
-
-          <detail-row :label="$options.i18n.STATUS_LABEL">{{ info.status }}</detail-row>
-
-          <detail-row :label="$options.i18n.EXPERIMENT_LABEL">
-            <gl-link :href="info.pathToExperiment">
-              {{ info.experimentName }}
-            </gl-link>
-          </detail-row>
-
-          <detail-row v-if="info.pathToArtifact" :label="$options.i18n.ARTIFACTS_LABEL">
-            <gl-link :href="info.pathToArtifact">
-              {{ $options.i18n.ARTIFACTS_LABEL }}
-            </gl-link>
-          </detail-row>
-        </tbody>
-      </table>
-    </section>
-
     <section class="gl-mb-6">
       <h3 :class="$options.HEADER_CLASSES">{{ $options.i18n.CI_SECTION_LABEL }}</h3>
 
diff --git a/app/assets/javascripts/ml/model_registry/components/model_version_performance.vue b/app/assets/javascripts/ml/model_registry/components/model_version_performance.vue
index 2eded91258271628618afa2fb2d271fd6b6a610a..a860375da68837dc2940197681d76b47135d63cf 100644
--- a/app/assets/javascripts/ml/model_registry/components/model_version_performance.vue
+++ b/app/assets/javascripts/ml/model_registry/components/model_version_performance.vue
@@ -53,7 +53,7 @@ export default {
           @click="copyMlflowId"
         />
       </p>
-      <candidate-detail :candidate="candidate" :show-info-section="false" />
+      <candidate-detail :candidate="candidate" />
     </div>
   </div>
 </template>
diff --git a/app/assets/javascripts/ml/model_registry/translations.js b/app/assets/javascripts/ml/model_registry/translations.js
index 14771a686acb1835128a901c932e2dc719dfb1eb..478af02ae6e026fde9ce4be72fc59a67a31723be 100644
--- a/app/assets/javascripts/ml/model_registry/translations.js
+++ b/app/assets/javascripts/ml/model_registry/translations.js
@@ -8,13 +8,15 @@ export const modelsCountLabel = (modelCount) =>
 export const DESCRIPTION_LABEL = __('Description');
 export const NO_DESCRIPTION_PROVIDED_LABEL = s__('MlModelRegistry|No description provided');
 export const INFO_LABEL = s__('MlModelRegistry|Info');
+export const DETAILS_LABEL = s__('MlModelRegistry|Details & Metadata');
 export const ID_LABEL = s__('MlModelRegistry|ID');
 export const MLFLOW_ID_LABEL = s__('MlModelRegistry|MLflow run ID');
 export const STATUS_LABEL = s__('MlModelRegistry|Status');
 export const EXPERIMENT_LABEL = s__('MlModelRegistry|Experiment');
 export const ARTIFACTS_LABEL = s__('MlModelRegistry|Artifacts');
+export const NO_ARTIFACTS_MESSAGE = s__('MlModelRegistry|No logged artifacts.');
 export const PARAMETERS_LABEL = s__('MlModelRegistry|Parameters');
-export const PERFORMANCE_LABEL = s__('MlModelRegistry|Model performance');
+export const PERFORMANCE_LABEL = s__('MlModelRegistry|Performance');
 export const METADATA_LABEL = s__('MlModelRegistry|Metadata');
 export const NO_PARAMETERS_MESSAGE = s__('MlModelRegistry|No logged parameters');
 export const NO_METRICS_MESSAGE = s__('MlModelRegistry|No logged metrics');
diff --git a/app/presenters/ml/candidate_details_presenter.rb b/app/presenters/ml/candidate_details_presenter.rb
index d881e8813c7a21baf03f29924e520e85383a3de6..d4f75cb447ff6b9064adb85775f8f61432f6798a 100644
--- a/app/presenters/ml/candidate_details_presenter.rb
+++ b/app/presenters/ml/candidate_details_presenter.rb
@@ -20,7 +20,10 @@ def present
             path_to_experiment: link_to_experiment,
             path: link_to_details,
             status: candidate.status,
-            ci_job: job_info
+            ci_job: job_info,
+            created_at: candidate.created_at,
+            authorWebUrl: candidate.user&.namespace&.web_url,
+            authorName: candidate.user&.name
           },
           params: candidate.params,
           metrics: candidate.metrics,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0e655a1b99e18fc5a9dbb498f409fd7ee0983fe6..848d4edf77212d6cfe7b0d25a5f6dc51c2831015 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -35426,6 +35426,9 @@ msgstr ""
 msgid "MlExperimentTracking|CI Job"
 msgstr ""
 
+msgid "MlExperimentTracking|Candidate %{id}"
+msgstr ""
+
 msgid "MlExperimentTracking|Candidate removed"
 msgstr ""
 
@@ -35507,9 +35510,6 @@ msgstr ""
 msgid "MlExperimentTracking|Machine learning experiment tracking"
 msgstr ""
 
-msgid "MlExperimentTracking|Model candidate details"
-msgstr ""
-
 msgid "MlExperimentTracking|Model experiments"
 msgstr ""
 
@@ -35552,6 +35552,9 @@ msgstr ""
 msgid "MlExperimentTracking|Version"
 msgstr ""
 
+msgid "MlExperimentTracking|by %{author}"
+msgstr ""
+
 msgid "MlModelRegistry|%d model"
 msgid_plural "MlModelRegistry|%d models"
 msgstr[0] ""
@@ -35658,6 +35661,9 @@ msgstr ""
 msgid "MlModelRegistry|Description"
 msgstr ""
 
+msgid "MlModelRegistry|Details & Metadata"
+msgstr ""
+
 msgid "MlModelRegistry|Drop or %{linkStart}select%{linkEnd} artifacts to attach"
 msgstr ""
 
@@ -35748,6 +35754,9 @@ msgstr ""
 msgid "MlModelRegistry|Metadata"
 msgstr ""
 
+msgid "MlModelRegistry|Metric"
+msgstr ""
+
 msgid "MlModelRegistry|Model card"
 msgstr ""
 
@@ -35760,9 +35769,6 @@ msgstr ""
 msgid "MlModelRegistry|Model name"
 msgstr ""
 
-msgid "MlModelRegistry|Model performance"
-msgstr ""
-
 msgid "MlModelRegistry|Model registry"
 msgstr ""
 
@@ -35799,6 +35805,9 @@ msgstr ""
 msgid "MlModelRegistry|No description provided"
 msgstr ""
 
+msgid "MlModelRegistry|No logged artifacts."
+msgstr ""
+
 msgid "MlModelRegistry|No logged metadata"
 msgstr ""
 
@@ -35838,6 +35847,9 @@ msgstr ""
 msgid "MlModelRegistry|Status"
 msgstr ""
 
+msgid "MlModelRegistry|Step %{step}"
+msgstr ""
+
 msgid "MlModelRegistry|Subfolder"
 msgstr ""
 
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/candidate_detail_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/candidate_detail_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..61ed60e72dda61296bb18601c71d7ef6ed590310
--- /dev/null
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/candidate_detail_spec.js
@@ -0,0 +1,177 @@
+import { GlAvatarLabeled, GlButton } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import CandidateDetail from '~/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue';
+import { newCandidate } from 'jest/ml/model_registry/mock_data';
+
+describe('ml/experiment_tracking/routes/candidates/show/candidate_detail.vue', () => {
+  let wrapper;
+
+  const defaultProps = {
+    candidate: newCandidate(),
+  };
+
+  const createWrapper = (props = {}) => {
+    return mountExtended(CandidateDetail, {
+      propsData: {
+        ...defaultProps,
+        ...props,
+      },
+    });
+  };
+
+  const findArtifactsTab = () => wrapper.findByTestId('artifacts');
+  const findAllTabs = () => wrapper.findAll('.gl-tab-nav-item');
+  const findMetricsTab = () => wrapper.findByTestId('metrics');
+  const findMlflowIdButton = () => wrapper.findComponent(GlButton);
+  const findMetricsTable = () => wrapper.findByTestId('metrics-table');
+  const findMetadata = () => wrapper.findByTestId('metadata');
+  const findMlflowRunId = () => wrapper.findByTestId('mlflow-run-id');
+  const findCiJobPathLink = () => wrapper.findByTestId('ci-job-path');
+  const findArtifactLink = () => wrapper.findByTestId('artifacts-link');
+  const findAvatarLabeled = () => wrapper.findComponent(GlAvatarLabeled);
+  const findParametersSection = () => wrapper.findByTestId('parameters');
+  const findParametersTable = () => wrapper.findByTestId('parameters-table');
+  const findCiSection = () => wrapper.findByTestId('ci');
+
+  describe('Basic rendering', () => {
+    beforeEach(() => {
+      wrapper = createWrapper();
+    });
+
+    it('renders all three tabs', () => {
+      const tabs = findAllTabs();
+      expect(tabs.at(0).text()).toBe('Details & Metadata');
+      expect(tabs.at(1).text()).toBe('Artifacts');
+      expect(tabs.at(2).text()).toBe('Performance');
+    });
+
+    it('displays MLflow run ID', () => {
+      expect(findMlflowRunId().text()).toBe('abcdefg');
+    });
+
+    it('renders metadata section', () => {
+      expect(findMetadata().text()).toContain('FileName');
+      expect(findMetadata().text()).toContain('test.py');
+    });
+  });
+
+  describe('Parameters section', () => {
+    beforeEach(() => {
+      wrapper = createWrapper();
+    });
+
+    it('renders parameters table when parameters exist', () => {
+      expect(findParametersSection().text()).toContain('Parameters');
+    });
+
+    it('renders metrics table with correct columns', () => {
+      const fields = findParametersTable().props('fields');
+      expect(fields.map((e) => e.label)).toEqual(['Name', 'Value']);
+    });
+
+    it('formats metrics data correctly', () => {
+      expect(findParametersTable().vm.$attrs.items).toEqual([
+        { name: 'Algorithm', value: 'Decision Tree' },
+        { name: 'MaxDepth', value: '3' },
+      ]);
+    });
+
+    it('shows no parameters message when parameters are empty', () => {
+      wrapper = createWrapper({
+        candidate: {
+          ...defaultProps.candidate,
+          params: [],
+        },
+      });
+      expect(findParametersSection().text()).toContain('No logged parameters');
+    });
+  });
+
+  describe('CI information section', () => {
+    it('renders CI job information when available', () => {
+      wrapper = createWrapper();
+      expect(findCiJobPathLink().text()).toContain('test');
+    });
+
+    it('renders user information when available', () => {
+      wrapper = createWrapper();
+      expect(findAvatarLabeled().text()).toContain('CI User');
+    });
+
+    it('shows no CI message when CI information is missing', () => {
+      wrapper = createWrapper({
+        candidate: {
+          ...defaultProps.candidate,
+          info: { ...defaultProps.candidate.info, ciJob: null },
+        },
+      });
+      expect(findCiSection().text()).toContain('Candidate not linked to a CI build');
+    });
+  });
+
+  describe('Metrics table', () => {
+    beforeEach(() => {
+      wrapper = createWrapper();
+    });
+
+    it('renders metrics table with correct columns', () => {
+      const fields = findMetricsTable().props('fields');
+      expect(fields.map((e) => e.label)).toEqual([
+        'Metric',
+        'Step 0',
+        'Step 1',
+        'Step 2',
+        'Step 3',
+      ]);
+    });
+
+    it('formats metrics data correctly', () => {
+      expect(findMetricsTable().vm.$attrs.items).toContainEqual({
+        name: 'Accuracy',
+        1: '.99',
+        2: '.98',
+        3: '.97',
+      });
+    });
+
+    it('shows no metrics message when metrics are empty', () => {
+      wrapper = createWrapper({
+        candidate: {
+          ...defaultProps.candidate,
+          metrics: [],
+        },
+      });
+      expect(findMetricsTab().text()).toContain('No logged metrics');
+    });
+  });
+
+  describe('MLflow ID copy button', () => {
+    beforeEach(() => {
+      wrapper = createWrapper();
+    });
+
+    it('copies MLflow ID to clipboard when clicked', async () => {
+      jest.spyOn(navigator.clipboard, 'writeText').mockImplementation(() => Promise.resolve());
+      await findMlflowIdButton().vm.$emit('click');
+      expect(navigator.clipboard.writeText).toHaveBeenCalledWith('abcdefg');
+      jest.restoreAllMocks();
+    });
+  });
+
+  describe('Artifacts tab', () => {
+    it('renders artifact link when available', () => {
+      wrapper = createWrapper();
+      expect(findArtifactLink().attributes('href')).toBe('path_to_artifact');
+    });
+
+    it('shows no artifacts message when artifact path is missing', () => {
+      wrapper = createWrapper({
+        candidate: {
+          ...defaultProps.candidate,
+          info: { ...defaultProps.candidate.info, pathToArtifact: null },
+        },
+      });
+      expect(findArtifactsTab().text()).toContain('No logged artifacts');
+    });
+  });
+});
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/candidate_header_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/candidate_header_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b0a46d8bc3828233bc2a0758686350377a0cfe63
--- /dev/null
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/candidate_header_spec.js
@@ -0,0 +1,129 @@
+import { GlBadge, GlIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import CandidateHeader from '~/ml/experiment_tracking/routes/candidates/show/candidate_header.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
+import PageHeading from '~/vue_shared/components/page_heading.vue';
+import { newCandidate } from 'jest/ml/model_registry/mock_data';
+
+describe('ml/experiment_tracking/routes/candidates/show/candidate_header.vue', () => {
+  let wrapper;
+
+  const defaultProps = {
+    info: newCandidate().info,
+  };
+
+  const createWrapper = (props = {}) => {
+    return shallowMountExtended(CandidateHeader, {
+      propsData: {
+        ...defaultProps,
+        ...props,
+      },
+    });
+  };
+
+  const findPageHeading = () => wrapper.findComponent(PageHeading);
+  const findDeleteButton = () => wrapper.findComponent(DeleteButton);
+  const findTimeAgo = () => wrapper.findComponent(TimeAgoTooltip);
+  const findBadge = () => wrapper.findComponent(GlBadge);
+  const findAuthorLink = () => wrapper.findByTestId('author-link');
+  const findExperimentLink = () => wrapper.findByTestId('experiment-link');
+  const findStatusIcon = () => wrapper.findComponent(GlIcon);
+
+  describe('Basic rendering', () => {
+    beforeEach(() => {
+      wrapper = createWrapper();
+    });
+
+    it('renders the page heading with correct title', () => {
+      expect(findPageHeading().text()).toContain('Candidate candidate_iid');
+    });
+
+    it('renders the experiment name with link', () => {
+      expect(findExperimentLink().text()).toBe('The Experiment /');
+      expect(findExperimentLink().attributes('href')).toBe('path/to/experiment');
+    });
+
+    it('renders the time ago component with correct timestamp', () => {
+      expect(findTimeAgo().props('time')).toBe('2024-01-01T00:00:00Z');
+    });
+
+    it('renders the status badge with correct variant', () => {
+      expect(findBadge().props('variant')).toBe('muted');
+      expect(findBadge().text()).toContain('SUCCESS');
+    });
+
+    it('renders the status icon', () => {
+      expect(findStatusIcon().exists()).toBe(true);
+      expect(findStatusIcon().props('name')).toBe('issue-type-test-case');
+    });
+
+    it('renders the author information', () => {
+      const authorLink = findAuthorLink();
+      expect(authorLink.attributes('href')).toBe('/test-user');
+      expect(authorLink.text()).toContain('by Test User');
+    });
+
+    it('renders the delete button component', () => {
+      const deleteButton = findDeleteButton();
+      expect(deleteButton.exists()).toBe(true);
+      expect(deleteButton.props('deletePath')).toBe('path_to_candidate');
+    });
+  });
+
+  describe('Status variants', () => {
+    const testCases = [
+      { status: 'running', variant: 'success' },
+      { status: 'scheduled', variant: 'info' },
+      { status: 'finished', variant: 'muted' },
+      { status: 'failed', variant: 'warning' },
+      { status: 'killed', variant: 'danger' },
+    ];
+
+    testCases.forEach(({ status, variant }) => {
+      it(`renders correct badge variant for ${status} status`, () => {
+        wrapper = createWrapper({
+          info: {
+            ...defaultProps.info,
+            status,
+          },
+        });
+
+        expect(findBadge().props('variant')).toBe(variant);
+      });
+    });
+  });
+
+  describe('When author is not provided', () => {
+    beforeEach(() => {
+      wrapper = createWrapper({
+        info: {
+          ...defaultProps.info,
+          authorName: null,
+          authorWebUrl: null,
+        },
+      });
+    });
+
+    it('does not render author link', () => {
+      expect(findAuthorLink().exists()).toBe(false);
+    });
+  });
+
+  describe('delete button configuration', () => {
+    beforeEach(() => {
+      wrapper = createWrapper();
+    });
+
+    it('passes correct props to delete button', () => {
+      const deleteButton = findDeleteButton();
+      expect(deleteButton.props()).toMatchObject({
+        deletePath: 'path_to_candidate',
+        deleteConfirmationText:
+          'Deleting this candidate will delete the associated parameters, metrics, and metadata.',
+        actionPrimaryText: 'Delete candidate',
+        modalTitle: 'Delete candidate?',
+      });
+    });
+  });
+});
diff --git a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
index 3999e906cec4ec78c36575e865b4366b8ede0247..8d95b43e7792584a7727cb106978b4d90fe22f2c 100644
--- a/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
@@ -1,39 +1,29 @@
 import { shallowMount } from '@vue/test-utils';
 import MlCandidatesShow from '~/ml/experiment_tracking/routes/candidates/show';
-import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
-import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
-import ModelExperimentsHeader from '~/ml/experiment_tracking/components/model_experiments_header.vue';
+import CandidateHeader from '~/ml/experiment_tracking/routes/candidates/show/candidate_header.vue';
+import CandidateDetail from '~/ml/experiment_tracking/routes/candidates/show/candidate_detail.vue';
 import { newCandidate } from 'jest/ml/model_registry/mock_data';
 
 describe('MlCandidatesShow', () => {
   let wrapper;
-  const CANDIDATE = newCandidate();
+  const candidate = newCandidate();
 
   const createWrapper = () => {
     wrapper = shallowMount(MlCandidatesShow, {
-      propsData: { candidate: CANDIDATE },
+      propsData: { candidate },
     });
   };
 
-  const findDeleteButton = () => wrapper.findComponent(DeleteButton);
-  const findHeader = () => wrapper.findComponent(ModelExperimentsHeader);
+  const findCandidateHeader = () => wrapper.findComponent(CandidateHeader);
   const findCandidateDetail = () => wrapper.findComponent(CandidateDetail);
 
   beforeEach(() => createWrapper());
 
-  it('shows delete button', () => {
-    expect(findDeleteButton().exists()).toBe(true);
-  });
-
-  it('passes the delete path to delete button', () => {
-    expect(findDeleteButton().props('deletePath')).toBe('path_to_candidate');
-  });
-
-  it('passes the right title', () => {
-    expect(findHeader().props('pageTitle')).toBe('Model candidate details');
+  it('creates the candidate header section', () => {
+    expect(findCandidateHeader().props('info')).toBe(candidate.info);
   });
 
   it('creates the candidate detail section', () => {
-    expect(findCandidateDetail().props('candidate')).toBe(CANDIDATE);
+    expect(findCandidateDetail().props('candidate')).toBe(candidate);
   });
 });
diff --git a/spec/frontend/ml/model_registry/components/candidate_detail_spec.js b/spec/frontend/ml/model_registry/components/candidate_detail_spec.js
index c6dcb534be89ae85d36af2b8391f4e90bbf19d05..eaffc169c8c04ddb6cb36ae05a2265c65231c8f9 100644
--- a/spec/frontend/ml/model_registry/components/candidate_detail_spec.js
+++ b/spec/frontend/ml/model_registry/components/candidate_detail_spec.js
@@ -16,14 +16,13 @@ describe('ml/model_registry/components/candidate_detail.vue', () => {
   const CANDIDATE = newCandidate();
   const USER_ROW = 1;
 
-  const INFO_SECTION = 0;
-  const CI_SECTION = 1;
-  const PARAMETER_SECTION = 2;
-  const METADATA_SECTION = 3;
+  const CI_SECTION = 0;
+  const PARAMETER_SECTION = 1;
+  const METADATA_SECTION = 2;
 
-  const createWrapper = (createCandidate = () => CANDIDATE, showInfoSection = true) => {
+  const createWrapper = (createCandidate = () => CANDIDATE) => {
     wrapper = shallowMountExtended(CandidateDetail, {
-      propsData: { candidate: createCandidate(), showInfoSection },
+      propsData: { candidate: createCandidate() },
       stubs: {
         GlTableLite: { ...stubComponent(GlTableLite), props: ['items', 'fields'] },
       },
@@ -47,11 +46,6 @@ describe('ml/model_registry/components/candidate_detail.vue', () => {
 
     const mrText = `!${CANDIDATE.info.ciJob.mergeRequest.iid} ${CANDIDATE.info.ciJob.mergeRequest.title}`;
     const expectedTable = [
-      [INFO_SECTION, 0, 'ID', CANDIDATE.info.iid],
-      [INFO_SECTION, 1, 'MLflow run ID', CANDIDATE.info.eid],
-      [INFO_SECTION, 2, 'Status', CANDIDATE.info.status],
-      [INFO_SECTION, 3, 'Experiment', CANDIDATE.info.experimentName],
-      [INFO_SECTION, 4, 'Artifacts', 'Artifacts'],
       [CI_SECTION, 0, 'Job', CANDIDATE.info.ciJob.name],
       [CI_SECTION, 1, 'Triggered by', 'CI User'],
       [CI_SECTION, 2, 'Merge request', mrText],
@@ -71,8 +65,6 @@ describe('ml/model_registry/components/candidate_detail.vue', () => {
 
     describe('Table links', () => {
       const linkRows = [
-        [INFO_SECTION, 3, CANDIDATE.info.pathToExperiment],
-        [INFO_SECTION, 4, CANDIDATE.info.pathToArtifact],
         [CI_SECTION, 0, CANDIDATE.info.ciJob.path],
         [CI_SECTION, 2, CANDIDATE.info.ciJob.mergeRequest.path],
       ];
@@ -181,12 +173,4 @@ describe('ml/model_registry/components/candidate_detail.vue', () => {
       expect(findLabel('Triggered by').exists()).toBe(false);
     });
   });
-
-  describe('showInfoSection is set to false', () => {
-    beforeEach(() => createWrapper(() => CANDIDATE, false));
-
-    it('does not render the info section', () => {
-      expect(findLabel('MLflow run ID').exists()).toBe(false);
-    });
-  });
 });
diff --git a/spec/frontend/ml/model_registry/mock_data.js b/spec/frontend/ml/model_registry/mock_data.js
index d8bb6a8eedbca73dadfae7ddbd3f9be84c2d82d4..e30208751b798da40d0b780a9f867501b60a5c89 100644
--- a/spec/frontend/ml/model_registry/mock_data.js
+++ b/spec/frontend/ml/model_registry/mock_data.js
@@ -37,6 +37,9 @@ export const newCandidate = () => ({
         avatar: '/img.png',
       },
     },
+    createdAt: '2024-01-01T00:00:00Z',
+    authorName: 'Test User',
+    authorWebUrl: '/test-user',
   },
 });
 
diff --git a/spec/presenters/ml/candidate_details_presenter_spec.rb b/spec/presenters/ml/candidate_details_presenter_spec.rb
index 98b152f727c9e6f454d49c890b9a8098eb9e4ad9..05b09d73221efe78ddd70ccf4ac25e551b5171e2 100644
--- a/spec/presenters/ml/candidate_details_presenter_spec.rb
+++ b/spec/presenters/ml/candidate_details_presenter_spec.rb
@@ -48,7 +48,7 @@
     subject { described_class.new(candidate, user).present }
 
     it 'presents the candidate correctly' do
-      is_expected.to eq(
+      is_expected.to match(
         {
           candidate: {
             info: {
@@ -73,7 +73,10 @@
                   path: "/#{pipeline.user.username}",
                   username: pipeline.user.username
                 }
-              }
+              },
+              created_at: candidate.created_at,
+              authorWebUrl: nil,
+              authorName: candidate.user.name
             },
             params: params,
             metrics: metrics,