diff --git a/integration/tests/grid-tooltip.test.js b/integration/tests/grid-tooltip.test.js
index ef332cc2659..95e793ea0a6 100644
--- a/integration/tests/grid-tooltip.test.js
+++ b/integration/tests/grid-tooltip.test.js
@@ -32,6 +32,7 @@ describe('tooltip', () => {
       <vaadin-grid>
         <vaadin-grid-column path="firstName"></vaadin-grid-column>
         <vaadin-grid-column path="lastName"></vaadin-grid-column>
+        <vaadin-grid-column path="lastName"></vaadin-grid-column>
         <vaadin-tooltip slot="tooltip"></vaadin-tooltip>
       </vaadin-grid>
     `);
@@ -262,6 +263,67 @@ describe('tooltip', () => {
     });
   });
 
+  describe('cell not fully visible', () => {
+    ['ltr', 'rtl'].forEach((direction) => {
+      describe(`${direction}`, () => {
+        const isRTL = direction === 'rtl';
+
+        before(() => {
+          document.documentElement.setAttribute('dir', direction);
+        });
+
+        after(() => {
+          document.documentElement.removeAttribute('dir');
+        });
+
+        beforeEach(async () => {
+          tooltip.hoverDelay = 0;
+          grid.style.width = '150px';
+          flushGrid(grid);
+          await nextRender();
+        });
+
+        it('should not show tooltip when cell not fully visible at the start', async () => {
+          grid.$.table.scrollLeft = isRTL ? -150 : 150;
+          await nextRender();
+          flushGrid(grid);
+
+          mouseenter(getCell(grid, 0));
+          expect(tooltip.opened).to.be.false;
+        });
+
+        it('should not show tooltip when cell not fully visible at the end', () => {
+          mouseenter(getCell(grid, 1));
+          expect(tooltip.opened).to.be.false;
+        });
+
+        it('should not show tooltip when cell is partially covered by frozen cell', async () => {
+          grid.querySelector('vaadin-grid-column').frozen = true;
+          await nextRender();
+
+          grid.$.table.scrollLeft = isRTL ? -100 : 100;
+          await nextRender();
+          flushGrid(grid);
+
+          mouseenter(getCell(grid, 1));
+          expect(tooltip.opened).to.be.false;
+        });
+
+        it('should not show tooltip when cell is partially covered by frozen to end cell', async () => {
+          grid.querySelectorAll('vaadin-grid-column')[2].frozenToEnd = true;
+          await nextRender();
+
+          grid.$.table.scrollLeft = isRTL ? -100 : 100;
+          await nextRender();
+          flushGrid(grid);
+
+          mouseenter(getCell(grid, 1));
+          expect(tooltip.opened).to.be.false;
+        });
+      });
+    });
+  });
+
   describe('delay', () => {
     before(() => {
       Tooltip.setDefaultFocusDelay(0);
diff --git a/packages/grid/src/vaadin-grid-mixin.js b/packages/grid/src/vaadin-grid-mixin.js
index 99bf8149976..8bde43fb6a4 100644
--- a/packages/grid/src/vaadin-grid-mixin.js
+++ b/packages/grid/src/vaadin-grid-mixin.js
@@ -1004,7 +1004,13 @@ export const GridMixin = (superClass) =>
       // Check if there is a slotted vaadin-tooltip element.
       const tooltip = this._tooltipController.node;
       if (tooltip && tooltip.isConnected) {
-        this._tooltipController.setTarget(event.target);
+        const target = event.target;
+
+        if (!this.__isCellFullyVisible(target)) {
+          return;
+        }
+
+        this._tooltipController.setTarget(target);
         this._tooltipController.setContext(this.getEventContext(event));
 
         // Trigger opening using the corresponding delay.
@@ -1015,6 +1021,33 @@ export const GridMixin = (superClass) =>
       }
     }
 
+    /** @private */
+    __isCellFullyVisible(cell) {
+      if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
+        // Frozen cells are always fully visible
+        return true;
+      }
+
+      let { left, right } = this.getBoundingClientRect();
+
+      const frozen = [...cell.parentNode.children].find((cell) => cell.hasAttribute('last-frozen'));
+      if (frozen) {
+        const frozenRect = frozen.getBoundingClientRect();
+        left = this.__isRTL ? left : frozenRect.right;
+        right = this.__isRTL ? frozenRect.left : right;
+      }
+
+      const frozenToEnd = [...cell.parentNode.children].find((cell) => cell.hasAttribute('first-frozen-to-end'));
+      if (frozenToEnd) {
+        const frozenToEndRect = frozenToEnd.getBoundingClientRect();
+        left = this.__isRTL ? frozenToEndRect.right : left;
+        right = this.__isRTL ? right : frozenToEndRect.left;
+      }
+
+      const cellRect = cell.getBoundingClientRect();
+      return cellRect.left >= left && cellRect.right <= right;
+    }
+
     /** @protected */
     _hideTooltip(immediate) {
       const tooltip = this._tooltipController && this._tooltipController.node;