diff --git a/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/AnalyticalTableFeatureExamples.stories.tsx b/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/AnalyticalTableFeatureExamples.stories.tsx
index b43b46b796b..5168a2e5e00 100644
--- a/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/AnalyticalTableFeatureExamples.stories.tsx
+++ b/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/AnalyticalTableFeatureExamples.stories.tsx
@@ -1319,3 +1319,64 @@ export const ColumnClassName: Story = {
);
},
};
+
+const borderlessColumns: AnalyticalTableColumnDefinition[] = [
+ { Header: 'Name', accessor: 'name' },
+ { Header: 'Age', accessor: 'age' },
+ { Header: 'Friend Name', accessor: 'friend.name' },
+ { Header: 'Friend Age', accessor: 'friend.age' },
+];
+
+export const BorderlessContainer: Story = {
+ tags: ['custom'],
+ render() {
+ return (
+ <>
+
+
+ >
+ );
+ },
+};
+
+export const NoVerticalCellBorders: Story = {
+ tags: ['custom'],
+ render() {
+ return (
+ <>
+
+
+ >
+ );
+ },
+};
diff --git a/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/FeatureExamples.mdx b/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/FeatureExamples.mdx
index d364d9ad302..dcf5fb0c2e8 100644
--- a/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/FeatureExamples.mdx
+++ b/packages/main/src/components/AnalyticalTable/docs/FeatureExamples/FeatureExamples.mdx
@@ -1053,4 +1053,40 @@ const columns = [
```
+## Custom Border Styles
+
+> **⚠️ Not backed by SAP Design:** there are no design specifications for a fully borderless Grid/AnalyticalTable — for example the [`sap.ui.table.Table`](https://ui5.sap.com/#/entity/sap.ui.table.Table) always renders its borders and exposes no API to remove them. Customizing them is therefore your own responsibility and can lead to visual inconsistencies with other tables in the system. The recipes below also rely on internal CSS custom properties and `data-*` hooks that may change between releases. Use with caution.
+
+There is no public prop for customizing the table's borders. The two opt-in recipes below apply a CSS class to the table — one removes the outer container frame, the other removes the vertical dividers between columns. Each class is applied to the table's root element.
+
+### Borderless Container
+
+Removes the outer container frame (top, inline-end and bottom borders). The class also covers the sibling vertical scrollbar; the cell and row borders are kept.
+
+
+
+```css
+.at-borderless-container {
+ --_ui5wcr-AnalyticalTable-OuterBorderBlock: none;
+ --_ui5wcr-AnalyticalTable-OuterBorderInline: none;
+ --sapList_TableFooterBorder: transparent;
+}
+.at-borderless-container [data-component-name='AnalyticalTableContainer']::after {
+ display: none;
+}
+```
+
+### Table Without Vertical Cell Borders
+
+Removes the vertical dividers between columns (header and body) while keeping the horizontal row separators.
+
+
+
+```css
+.at-no-vertical-cell-borders [role='columnheader'],
+.at-no-vertical-cell-borders [data-column-id-cell] {
+ border-inline-end: none;
+}
+```
+
diff --git a/packages/main/src/components/AnalyticalTable/test/AnalyticalTable.gallery.tsx b/packages/main/src/components/AnalyticalTable/test/AnalyticalTable.gallery.tsx
index 94873dc3a45..9d1c6393f5e 100644
--- a/packages/main/src/components/AnalyticalTable/test/AnalyticalTable.gallery.tsx
+++ b/packages/main/src/components/AnalyticalTable/test/AnalyticalTable.gallery.tsx
@@ -1,4 +1,5 @@
import { useCallback, useMemo, useRef, useState } from 'react';
+import { AnalyticalTableSelectionMode } from '../../../enums/AnalyticalTableSelectionMode.js';
import { Panel } from '../../../webComponents/Panel/index.js';
import { Tab } from '../../../webComponents/Tab/index.js';
import { TabContainer } from '../../../webComponents/TabContainer/index.js';
@@ -211,3 +212,63 @@ export const ColumnsFitWidthHideRevealTestComp = () => {
);
};
+
+const borderlessData = Array.from({ length: 20 }, (_, i) => ({
+ name: `Name-${i}`,
+ age: i,
+ friend: { name: `Friend-${i}`, age: i + 100 },
+}));
+
+const borderlessColumns: AnalyticalTableColumnDefinition[] = [
+ { Header: 'Name', accessor: 'name' },
+ { Header: 'Age', accessor: 'age' },
+ { Header: 'Friend Name', accessor: 'friend.name' },
+];
+
+// A/B harness for the documented borderless recipes: `default` is the untouched baseline, `outer` removes the
+// container frame, `vertical` removes the inter-column cell borders. 20 rows / visibleRows 5 → the custom vertical
+// scrollbar renders.
+export const BorderlessHarness = () => (
+ <>
+
+
+
+
+ >
+);
diff --git a/packages/main/src/components/AnalyticalTable/test/AnalyticalTableBorderless.spec.tsx b/packages/main/src/components/AnalyticalTable/test/AnalyticalTableBorderless.spec.tsx
new file mode 100644
index 00000000000..7f7787a3ba6
--- /dev/null
+++ b/packages/main/src/components/AnalyticalTable/test/AnalyticalTableBorderless.spec.tsx
@@ -0,0 +1,72 @@
+import type { Locator } from '@playwright/test';
+import { expect, test } from '../../../../../../playwright/fixtures/gallery-fixtures.js';
+import type { BorderlessHarness } from './AnalyticalTable.gallery.js';
+
+const STORY = 'AnalyticalTable/BorderlessHarness';
+
+const container = (scope: Locator) => scope.locator('[data-component-name="AnalyticalTableContainer"]');
+const afterDisplay = (scope: Locator) => container(scope).evaluate((el) => getComputedStyle(el, '::after').display);
+// "Age" is a middle column, so its body cell always has the inter-column divider (border-inline-end).
+const cellDivider = (scope: Locator) =>
+ scope
+ .locator('[data-column-id-cell="age"]')
+ .first()
+ .evaluate((el) => getComputedStyle(el).borderInlineEndStyle);
+
+// Two documented, class-based recipes, each asserted against the untouched baseline so the test can't pass on a
+// no-op: `at-borderless-container` removes only the outer frame (keeping cell borders); `at-no-vertical-cell-borders`
+// removes only the inter-column dividers (keeping the frame).
+test.describe('AnalyticalTable borderless recipes', () => {
+ test('borderless container removes the outer ::after line but keeps cell dividers', async ({ mount, page }) => {
+ await mount(STORY);
+ await expect(page.getByText('Name-0').first()).toBeVisible();
+
+ expect(await afterDisplay(page.getByTestId('outer'))).toBe('none');
+ expect(await afterDisplay(page.getByTestId('default'))).not.toBe('none');
+ // cell dividers must stay
+ expect(await cellDivider(page.getByTestId('outer'))).toBe('solid');
+ });
+
+ test('no-vertical-cell-borders removes the dividers but keeps the outer frame', async ({ mount, page }) => {
+ await mount(STORY);
+ await expect(page.getByText('Name-0').first()).toBeVisible();
+
+ expect(await cellDivider(page.getByTestId('vertical'))).toBe('none');
+ expect(await cellDivider(page.getByTestId('default'))).toBe('solid');
+ // the outer frame must stay
+ expect(await afterDisplay(page.getByTestId('vertical'))).not.toBe('none');
+ });
+
+ test('the outer-border custom properties the recipe relies on still resolve to "none" in Horizon', async ({
+ mount,
+ page,
+ }) => {
+ await mount(STORY);
+ await expect(page.getByText('Name-0').first()).toBeVisible();
+
+ // Read the theme-provided values off the untouched baseline (not the recipe), so this breaks if the vars are
+ // renamed/removed (getPropertyValue → '') or if Horizon stops defaulting them to `none`.
+ const vars = await container(page.getByTestId('default')).evaluate((el) => {
+ const cs = getComputedStyle(el);
+ return {
+ block: cs.getPropertyValue('--_ui5wcr-AnalyticalTable-OuterBorderBlock').trim(),
+ inline: cs.getPropertyValue('--_ui5wcr-AnalyticalTable-OuterBorderInline').trim(),
+ };
+ });
+ expect(vars.block).toBe('none');
+ expect(vars.inline).toBe('none');
+ });
+
+ test('borderless container makes the vertical scrollbar bottom border transparent', async ({ mount, page }) => {
+ await mount(STORY);
+ await expect(page.getByText('Name-0').first()).toBeVisible();
+
+ const scrollbar = page.getByTestId('outer').locator('[data-component-name="AnalyticalTableVerticalScrollbar"]');
+ // The custom scrollbar only renders with native (non-overlay) scrollbars; skip the assertion otherwise.
+ if ((await scrollbar.count()) === 0) {
+ test.skip(true, 'custom vertical scrollbar not rendered (overlay scrollbars)');
+ }
+ const color = await scrollbar.evaluate((el) => getComputedStyle(el).borderBlockEndColor);
+ expect(color).toBe('rgba(0, 0, 0, 0)');
+ });
+});