diff --git a/packages/ibm-products-web-components/.storybook/_container.scss b/packages/ibm-products-web-components/.storybook/_container.scss
index 34b2db0b13f..f09b1bcfee3 100644
--- a/packages/ibm-products-web-components/.storybook/_container.scss
+++ b/packages/ibm-products-web-components/.storybook/_container.scss
@@ -7,7 +7,9 @@
// LICENSE file in the root directory of this source tree.
//
-@use '@carbon/styles';
+@use '@carbon/styles' with (
+ $font-path: '@ibm/plex'
+);
@use '@carbon/styles/scss/config' as *;
@use '@carbon/themes/scss/themes';
@use '@carbon/styles/scss/theme';
@@ -60,6 +62,10 @@ body {
padding: 0;
}
+.sbdocs-preview {
+ background-color: theme.$background;
+}
+
#main-content {
position: relative;
/* stylelint-disable-next-line carbon/layout-use */
diff --git a/packages/ibm-products-web-components/examples/truncated-text/.gitignore b/packages/ibm-products-web-components/examples/truncated-text/.gitignore
new file mode 100644
index 00000000000..a547bf36d8d
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/packages/ibm-products-web-components/examples/truncated-text/.sassrc b/packages/ibm-products-web-components/examples/truncated-text/.sassrc
new file mode 100644
index 00000000000..956b9e0a3d8
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/.sassrc
@@ -0,0 +1,6 @@
+{
+ "includePaths": [
+ "node_modules",
+ "../../node_modules"
+ ]
+}
\ No newline at end of file
diff --git a/packages/ibm-products-web-components/examples/truncated-text/gallery.config.json b/packages/ibm-products-web-components/examples/truncated-text/gallery.config.json
new file mode 100644
index 00000000000..94e9a8979a4
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/gallery.config.json
@@ -0,0 +1,4 @@
+{
+ "label": "Truncated Text",
+ "template": "node"
+}
diff --git a/packages/ibm-products-web-components/examples/truncated-text/index.html b/packages/ibm-products-web-components/examples/truncated-text/index.html
new file mode 100644
index 00000000000..db5e4abb2c1
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+ carbon-web-components example
+
+
+
+
+
+
+
+
diff --git a/packages/ibm-products-web-components/examples/truncated-text/package.json b/packages/ibm-products-web-components/examples/truncated-text/package.json
new file mode 100644
index 00000000000..f2ce6611ce5
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "truncated-text",
+ "version": "0.1.0",
+ "private": true,
+ "description": "Sample project for getting started with the Web Components from Carbon for IBM Products.",
+ "license": "Apache-2",
+ "main": "index.html",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "clean": "rimraf node_modules dist .cache"
+ },
+ "dependencies": {
+ "@carbon/ibm-products-web-components": "^0.10.0-rc.0",
+ "lit": "^3.2.1",
+ "sass": "^1.64.1"
+ },
+ "devDependencies": {
+ "rimraf": "^3.0.2",
+ "vite": "5.4.18"
+ }
+}
diff --git a/packages/ibm-products-web-components/examples/truncated-text/src/index.js b/packages/ibm-products-web-components/examples/truncated-text/src/index.js
new file mode 100644
index 00000000000..20389005020
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/src/index.js
@@ -0,0 +1,10 @@
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import '@carbon/ibm-products-web-components/es/components/truncated-text/index.js';
diff --git a/packages/ibm-products-web-components/examples/truncated-text/src/styles.scss b/packages/ibm-products-web-components/examples/truncated-text/src/styles.scss
new file mode 100644
index 00000000000..3e4b5ea9e3e
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/src/styles.scss
@@ -0,0 +1,18 @@
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+@use '@carbon/styles/scss/reset';
+@use '@carbon/styles/scss/theme';
+@use '@carbon/styles/scss/themes';
+
+:root {
+ @include theme.theme(themes.$white);
+ background-color: var(--cds-background);
+ color: var(--cds-text-primary);
+}
diff --git a/packages/ibm-products-web-components/examples/truncated-text/vite.config.js b/packages/ibm-products-web-components/examples/truncated-text/vite.config.js
new file mode 100644
index 00000000000..bdd7811a68f
--- /dev/null
+++ b/packages/ibm-products-web-components/examples/truncated-text/vite.config.js
@@ -0,0 +1,21 @@
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import { resolve } from 'path';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ build: {
+ rollupOptions: {
+ input: {
+ main: resolve(__dirname, 'index.html'),
+ },
+ },
+ },
+});
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/index.ts b/packages/ibm-products-web-components/src/components/truncated-text/index.ts
new file mode 100644
index 00000000000..944e80fab3c
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/index.ts
@@ -0,0 +1,10 @@
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2025, 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import './truncated-text';
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/story-styles.scss b/packages/ibm-products-web-components/src/components/truncated-text/story-styles.scss
new file mode 100644
index 00000000000..8112e82c787
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/story-styles.scss
@@ -0,0 +1,10 @@
+/*
+* Copyright IBM Corp. 2025, 2025
+*
+* This source code is licensed under the Apache-2.0 license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+@use '@carbon/styles/scss/theme' as *;
+
+$story-prefix: 'truncated-text-stories__';
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.mdx b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.mdx
new file mode 100644
index 00000000000..ef055a1b484
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.mdx
@@ -0,0 +1,56 @@
+import { ArgTypes, Markdown, Meta } from '@storybook/blocks';
+import { Canvas } from '@storybook/addon-docs';
+import { cdnJs, cdnCss } from '../../globals/internal/storybook-cdn';
+import * as TruncatedText from './truncated-text.stories';
+
+
+
+# TruncatedText
+
+The `TruncatedText` component truncates text based on a specified number of
+lines, provided via a prop. It offers two configurable options for revealing the
+full content:
+
+Tooltip Mode `with="tooltip"`: Displays the full text in a tooltip overlay on
+hover.
+
+Expandable Mode `with="expand"`: Reveals the complete content through
+collapsible "Read more" / "Read less" toggles. these labels can be customized
+with `expand-label`, `collapse-label` attributes on the component
+
+> 💡 Check our
+> [Stackblitz](https://stackblitz.com/github/carbon-design-system/ibm-products/tree/main/packages/ibm-products-web-components/examples/truncated-text)
+> example implementation.
+> [](https://stackblitz.com/github/carbon-design-system/ibm-products/tree/main/packages/ibm-products-web-components/examples/truncated-text)
+
+## Default with tooltip
+
+
+
+## With expand
+
+
+
+## Getting started
+
+Here's a quick example to get you started.
+
+### JS (via import)
+
+```javascript
+import '@carbon/ibm-products-web-components/es/components/truncated-text/index.js';
+```
+
+### HTML
+
+```html
+
+```
+
+## `` attributes, properties and events
+
+
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.scss b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.scss
new file mode 100644
index 00000000000..c8fe6a97ccb
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.scss
@@ -0,0 +1,110 @@
+/*
+* Copyright IBM Corp. 2025, 2025
+*
+* This source code is licensed under the Apache-2.0 license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+// $css--plex: true !default;
+
+@use 'sass:map';
+@use '@carbon/styles/scss/utilities' as *;
+@use '@carbon/themes' as *;
+@use '@carbon/styles/scss/theme' as *;
+@use '@carbon/styles/scss/motion' as *;
+
+$prefix: 'c4p';
+$carbon-prefix: 'cds';
+$block-class: #{$prefix}--truncated-text;
+
+// Common reset button style
+%reset-button {
+ border: none;
+ cursor: pointer;
+ font: inherit;
+ line-height: inherit;
+}
+
+// Mixin for directional gradients
+@mixin gradient-bg($direction, $color) {
+ background: linear-gradient(
+ $direction,
+ rgba(255, 255, 255, 0) 0%,
+ $color 90%
+ );
+}
+
+:host(#{$prefix}-truncated-text) {
+ display: block;
+ inline-size: 100%;
+
+ .#{$block-class}_transition {
+ @media (prefers-reduced-motion: no-preference) {
+ transition: max-height $duration-fast-02 motion(standard, productive);
+ }
+ }
+
+ .#{$block-class}_content {
+ display: -webkit-box;
+ overflow: hidden;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: var(--line-clamp, 0);
+ text-overflow: clip;
+ white-space: normal;
+
+ &:focus {
+ @include focus-outline('outline');
+ }
+ }
+
+ #{$carbon-prefix}-tooltip {
+ display: inline-flex;
+ }
+
+ button:focus,
+ .#{$block-class}_expand:focus,
+ .#{$block-class}_collapse:focus {
+ @include focus-outline('outline');
+ }
+}
+
+.#{$block-class}_expand,
+.#{$block-class}_collapse {
+ @extend %reset-button;
+
+ padding: 0;
+ color: $link-primary;
+}
+
+.#{$block-class}_expand {
+ position: absolute;
+ background-color: $background;
+ inset-block-end: 0;
+ inset-inline-end: 0;
+
+ &.#{$block-class}_layered {
+ background-color: $layer;
+ }
+
+ // Gradient LTR
+ &:dir(ltr)::before {
+ @include gradient-bg(90deg, $background);
+ }
+
+ &.#{$block-class}_layered:dir(ltr)::before {
+ @include gradient-bg(90deg, $layer);
+ }
+
+ // Gradient RTL
+ &:dir(rtl)::before {
+ @include gradient-bg(270deg, $background);
+ }
+
+ &.#{$block-class}_layered:dir(rtl)::before {
+ @include gradient-bg(270deg, $layer);
+ }
+}
+
+.#{$block-class}_collapse {
+ background-color: transparent;
+}
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.stories.ts b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.stories.ts
new file mode 100644
index 00000000000..fcb9c9f3a27
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.stories.ts
@@ -0,0 +1,284 @@
+//cspell: disable
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2025, 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import { html } from 'lit';
+import './index';
+import mdx from './truncated-text.mdx';
+import CDSTruncatedText from './truncated-text';
+import '@carbon/web-components/es/components/layer/layer.js';
+
+const storyPrefix = 'truncated-text-stories__';
+const defaultArgs = {
+ lines: 2,
+ value:
+ 'Buttons are used to initialize an action, either in the background or foreground of an experience. There are several kinds of buttons. Primary buttons should be used for the principal call to action on the page. Secondary buttons should be used for secondary actions on each page. Danger buttons should be used for a negative action (such as Delete) on the page. Modify the behavior of the button by changing its event properties. Small buttons may be used when there is not enough space for a regular-sized button. This issue is most often found in tables. Small buttons should have three words or fewer.',
+ element: 'p',
+};
+
+const argTypes = {
+ lines: {
+ control: {
+ type: 'number',
+ },
+ },
+ value: {
+ control: {
+ type: 'text',
+ },
+ },
+ with: {
+ control: { type: 'select' },
+ options: ['tooltip', 'expand'],
+ },
+ expandLabel: {
+ table: {
+ disable: true,
+ },
+ control: {
+ type: 'text',
+ },
+ },
+ collapseLabel: {
+ table: {
+ disable: true,
+ },
+ control: {
+ type: 'text',
+ },
+ },
+ element: {
+ description:
+ 'This is a story-only control to show the Truncated Text in different HTML elements, which just sets the parent element of the Truncated Text.',
+ control: {
+ type: 'select',
+ labels: {
+ p: 'Paragraph',
+ layers: 'With layers',
+ h1: 'Heading 1',
+ h2: 'Heading 2',
+ h3: 'Heading 3',
+ h4: 'Heading 4',
+ h5: 'Heading 5',
+ h6: 'Heading 6',
+ },
+ },
+ options: ['p', 'layers', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ },
+};
+
+const renderTemplate = (args) => {
+ const { lines, value, with: withMode } = args;
+ const expandLabelAttr = args['expand-label'];
+ const collapseLabelAttr = args['collapse-label'];
+ return html`
+ ${args.element === 'p'
+ ? html`
+
+
`
+ : ''}
+ ${args.element === 'layers'
+ ? html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+ : ''}
+ ${args.element === 'h1'
+ ? html`
+
+
`
+ : ''}
+ ${args.element === 'h2'
+ ? html`
+
+
`
+ : ''}
+ ${args.element === 'h3'
+ ? html`
+
+
`
+ : ''}
+ ${args.element === 'h4'
+ ? html`
+
+
`
+ : ''}
+ ${args.element === 'h5'
+ ? html`
+
+
`
+ : ''}
+ ${args.element === 'h6'
+ ? html`
+
+
`
+ : ''}
+ `;
+};
+
+export const Default = {
+ args: {
+ ...defaultArgs,
+ },
+ argTypes,
+ render: renderTemplate,
+ parameters: {
+ docs: {
+ source: {
+ code: `
+
+ `,
+ },
+ },
+ },
+};
+
+export const WithTooltip = {
+ args: {
+ ...defaultArgs,
+ with: 'tooltip',
+ },
+ argTypes,
+ render: renderTemplate,
+ parameters: {
+ docs: {
+ source: {
+ code: `
+
+ `,
+ },
+ },
+ },
+};
+
+export const WithExpand = {
+ args: {
+ ...defaultArgs,
+ with: 'expand',
+ },
+ argTypes,
+ render: renderTemplate,
+ parameters: {
+ docs: {
+ source: {
+ code: `
+
+ `,
+ },
+ },
+ },
+};
+
+const meta = {
+ title: 'Experimental/TruncatedText',
+ component: 'c4p-truncated-text',
+};
+
+export default meta;
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.test.ts b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.test.ts
new file mode 100644
index 00000000000..14b7f14397c
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.test.ts
@@ -0,0 +1,88 @@
+/**
+ * Copyright IBM Corp. 2025, 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import { describe, it, expect } from 'vitest';
+import { fixture, html } from '@open-wc/testing';
+import CDSTruncatedText from './truncated-text';
+import './index';
+
+const defaultProps = {
+ lines: 2,
+ text: 'Buttons are used to initialize an action, either in the background or foreground of an experience. There are several kinds of buttons. Primary buttons should be used for the principle call to action on the page.',
+ with: '',
+};
+
+const template = (props = defaultProps, templateWidth?: number) => html`
+
+
+
+`;
+
+describe('c4p-truncated-text', () => {
+ it('renders the component', async () => {
+ const wrapper = await fixture(template());
+ const el = wrapper.querySelector('c4p-truncated-text');
+ expect(el).toBeTruthy();
+ });
+
+ it('renders a tooltip when text is truncated with tooltip', async () => {
+ const wrapper = await fixture(
+ template({ ...defaultProps, with: 'tooltip' }, 200)
+ );
+
+ const el = wrapper.querySelector('c4p-truncated-text') as CDSTruncatedText;
+ const tooltip = el.shadowRoot?.querySelector('cds-tooltip');
+ expect(tooltip).toBeTruthy();
+ });
+
+ it('does not render a tooltip if the text fits', async () => {
+ const wrapper = await fixture(
+ template({ ...defaultProps, with: 'tooltip' }, 9000)
+ );
+
+ const el = wrapper.querySelector('c4p-truncated-text') as CDSTruncatedText;
+
+ const tooltip = el.shadowRoot?.querySelector('cds-tooltip');
+ expect(tooltip).not.toBeTruthy();
+ });
+
+ it('tests lines prop/attribute with tooltip', async () => {
+ for (let lines = 1; lines <= 4; lines++) {
+ const wrapper = await fixture(
+ template({ ...defaultProps, lines, with: 'tooltip' }, 600)
+ );
+
+ const el = wrapper.querySelector(
+ 'c4p-truncated-text'
+ ) as CDSTruncatedText;
+ await el.updateComplete;
+
+ const tooltip = el.shadowRoot?.querySelector('cds-tooltip');
+ if (lines <= 2) {
+ expect(tooltip).toBeTruthy();
+ } else {
+ expect(tooltip).not.toBeTruthy();
+ }
+ }
+ });
+
+ it('renders a expandable button when text is truncated with expand', async () => {
+ const wrapper = await fixture(
+ template({ ...defaultProps, lines: 2, with: 'expand' }, 400)
+ );
+ const el = wrapper.querySelector('c4p-truncated-text') as CDSTruncatedText;
+ await el.updateComplete;
+ const expandButton = el.shadowRoot?.querySelector(
+ '.c4p--truncated-text_expand'
+ );
+ expect(expandButton).toBeTruthy();
+ });
+});
diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.ts b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.ts
new file mode 100644
index 00000000000..67b80809623
--- /dev/null
+++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.ts
@@ -0,0 +1,211 @@
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2025, 2025
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import { html, LitElement } from 'lit';
+import { property, query, state } from 'lit/decorators.js';
+import { classMap } from 'lit/directives/class-map.js';
+import { carbonElement as customElement } from '@carbon/web-components/es/globals/decorators/carbon-element.js';
+import { prefix, carbonPrefix } from '../../globals/settings';
+import '@carbon/web-components/es/components/tooltip/index.js';
+import '@carbon/web-components/es/components/button/button.js';
+import '@carbon/web-components/es/components/link/index.js';
+
+import styles from './truncated-text.scss?lit';
+
+const componentName = 'truncated-text';
+export const blockClass = `${prefix}--${componentName}`;
+const elementName = `${prefix}-${componentName}`; // c4p-truncated-text
+
+/**
+ * TruncatedText.
+ *
+ * @element c4p-truncated-text
+ */
+@customElement(elementName)
+export class CDSTruncatedText extends LitElement {
+ /**
+ * The maximum number of lines to display before truncation.
+ */
+ @property({ type: Number, reflect: true }) lines = 0;
+
+ /**
+ * The string value to be truncated.
+ */
+ @property({ type: String, attribute: 'value', reflect: true }) value = '';
+
+ /**
+ * The label on expand button.
+ */
+ @property({ attribute: 'expand-label', type: String, reflect: true })
+ expandLabel = '...more';
+
+ /**
+ * The label on the collapse button.
+ */
+ @property({ attribute: 'collapse-label', type: String, reflect: true })
+ collapseLabel = '...less';
+
+ /**
+ * The method to display the full text when truncated. Options are "tooltip" or "expand". if not passed, the text would just be truncated with ellipsis.
+ */
+ @property({ type: String, reflect: true }) with: 'tooltip' | 'expand' | null =
+ null;
+
+ @state() private _isOverflowing: boolean = false;
+ @state() private _isExpanded: boolean = false;
+
+ @query(`.${blockClass}_content`) private _textElement!: HTMLElement;
+ private _lineHeight: number = 0;
+ private _isLayered: boolean = false;
+ private _resizeObserver?: ResizeObserver;
+
+ static styles = styles;
+
+ connectedCallback() {
+ super.connectedCallback();
+ this._isLayered = !!this.closest(`${carbonPrefix}-layer`);
+ }
+
+ disconnectedCallback() {
+ this._resizeObserver?.disconnect();
+ super.disconnectedCallback();
+ }
+
+ protected firstUpdated() {
+ requestAnimationFrame(() => {
+ const computedStyle = getComputedStyle(this._textElement);
+ this._lineHeight = parseFloat(computedStyle.lineHeight);
+ this._setupResizeObserver();
+ });
+ }
+
+ private _setupResizeObserver() {
+ const el = this._textElement;
+ if (!el) {
+ return;
+ }
+
+ let lastWidth = el.offsetWidth;
+
+ if (this._resizeObserver) {
+ this._resizeObserver.disconnect();
+ }
+
+ this._resizeObserver = new ResizeObserver(([entry]) => {
+ const newWidth = entry.contentRect.width;
+ if (newWidth !== lastWidth) {
+ lastWidth = newWidth;
+ this._updateOverflowStatus();
+ }
+ });
+
+ this._resizeObserver.observe(el);
+ }
+
+ protected updated(changed: Map) {
+ if (changed.has('lines') || changed.has('value')) {
+ this._updateOverflowStatus();
+ }
+ }
+
+ private _updateOverflowStatus() {
+ if (!this._textElement || this.lines <= 0) {
+ return;
+ }
+ setTimeout(() => {
+ const { scrollHeight, clientHeight } = this._textElement;
+ const buffer = Math.ceil(
+ clientHeight / (2 * Math.max(1, this.lines || 1))
+ );
+ const isOverflowing = scrollHeight > clientHeight + buffer;
+
+ if (isOverflowing !== this._isOverflowing) {
+ this._isOverflowing = isOverflowing;
+ }
+ });
+ }
+
+ private _toggleExpansion() {
+ this._isExpanded = !this._isExpanded;
+ this._textElement?.classList.add(`${blockClass}_transition`);
+ const onTransitionEnd = () => {
+ this._textElement?.querySelector('button')?.focus();
+ this._textElement?.removeEventListener('transitionend', onTransitionEnd);
+ };
+ this._textElement?.addEventListener('transitionend', onTransitionEnd);
+ }
+
+ private _renderToggleButton() {
+ if (!this._isOverflowing && !this._isExpanded) {
+ return;
+ }
+ const className = classMap({
+ [`${blockClass}_collapse`]: this._isExpanded,
+ [`${blockClass}_expand`]: !this._isExpanded,
+ [`${blockClass}_layered`]: this._isLayered,
+ });
+ const label = this._isExpanded
+ ? this.collapseLabel || '...less'
+ : this.expandLabel || '...more';
+ return html`
+
+ `;
+ }
+
+ render() {
+ // Calculate max height based on line height and number of lines
+ const maxHeight =
+ this.lines > 0 && !this._isExpanded
+ ? `${this.lines * this._lineHeight}px`
+ : `${this._textElement?.scrollHeight}px`;
+
+ // Apply different styles based on truncation method
+ const contentStyle =
+ this.with === 'tooltip' || !this.with
+ ? `--line-clamp: ${this._isExpanded ? 'none' : this.lines};`
+ : `max-block-size: ${maxHeight};`;
+
+ const contentClass = classMap({
+ [`${blockClass}_content`]: true,
+ });
+
+ const content = html`
+
+ ${this.value}
+ ${this.with === 'expand' ? this._renderToggleButton() : null}
+
+ `;
+
+ return this.with === 'tooltip' && this._isOverflowing
+ ? html`
+
+ ${content}
+ ${this.value}
+
+ `
+ : html`
+ ${content}
+
`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ [elementName]: CDSTruncatedText;
+ }
+}
+
+export default CDSTruncatedText;
diff --git a/packages/ibm-products-web-components/src/index.ts b/packages/ibm-products-web-components/src/index.ts
index 3528ded1075..efbcef15a32 100644
--- a/packages/ibm-products-web-components/src/index.ts
+++ b/packages/ibm-products-web-components/src/index.ts
@@ -13,3 +13,4 @@ export { default as CDSFullPageError } from './components/full-page-error/full-p
export { default as CDSAboutModal } from './components/about-modal/about-modal';
export { default as CDSUseravatar } from './components/user-avatar/user-avatar';
export { default as CDSOptionsTile } from './components/options-tile/options-tile';
+export { default as CDSTruncatedText } from './components/truncated-text/truncated-text';