diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index 6fb5c0e790bf..e56d006efee3 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -24,6 +24,7 @@ import {
screen,
} from 'spec/helpers/testing-library';
import { FeatureFlag } from '@superset-ui/core';
+import { supersetTheme } from '@apache-superset/core/theme';
import {
OPEN_FILTER_BAR_WIDTH,
CLOSED_FILTER_BAR_WIDTH,
@@ -487,6 +488,47 @@ test('should render ParentSize wrapper with height 100% for tabs', async () => {
expect(tabPanels.length).toBeGreaterThan(0);
});
+test('should apply min-height to the top-level tab drop target so tabs can be dropped on dashboards with content', () => {
+ (useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
+ 100,
+ jest.fn(),
+ ]);
+ (fetchFaveStar as jest.Mock).mockReturnValue({ type: 'mock-action' });
+ (setActiveTab as jest.Mock).mockReturnValue({ type: 'mock-action' });
+
+ const { getByTestId } = render(, {
+ useRedux: true,
+ store: storeWithState({
+ ...mockState,
+ dashboardLayout: undoableDashboardLayout,
+ dashboardState: { ...mockState.dashboardState, editMode: true },
+ }),
+ useDnd: true,
+ useTheme: true,
+ });
+
+ const headerWrapper = getByTestId('dashboard-header-wrapper');
+
+ // The Droppable inside the header should have the empty-droptarget class
+ // when there are no top-level tabs and edit mode is active. Without this
+ // class (and its associated min-height CSS rule), the drop target has zero
+ // height and users cannot drag tabs onto dashboards that already have
+ // content.
+ const droptarget = headerWrapper.querySelector('.empty-droptarget');
+ expect(droptarget).toBeInTheDocument();
+
+ // Verify the StyledHeader CSS defines a non-zero min-height for
+ // .empty-droptarget, derived from theme.sizeUnit * 4 to stay in sync
+ // with the source rule in DashboardBuilder.tsx.
+ expect(headerWrapper).toHaveStyleRule(
+ 'min-height',
+ `${supersetTheme.sizeUnit * 4}px`,
+ {
+ target: '.empty-droptarget',
+ },
+ );
+});
+
test('should maintain layout when switching between tabs', async () => {
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
100,
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index 8906ddb316e4..2013ff074cb5 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -100,6 +100,10 @@ const StyledHeader = styled.div<{ filterBarWidth: number }>`
z-index: 99;
max-width: calc(100vw - ${filterBarWidth}px);
+ .empty-droptarget {
+ min-height: ${theme.sizeUnit * 4}px;
+ }
+
.empty-droptarget:before {
position: absolute;
content: '';
diff --git a/superset-frontend/src/types/emotion-jest.d.ts b/superset-frontend/src/types/emotion-jest.d.ts
new file mode 100644
index 000000000000..c6d15cfb2ddd
--- /dev/null
+++ b/superset-frontend/src/types/emotion-jest.d.ts
@@ -0,0 +1,20 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+///