Repository: Textualize/textual

Issue:
Add ability to remove cell padding
I haven't been able to modify the style of a DataTable so that there is zero space between the columns. This in contrast to the rows, which are stacked with zero space between the cells. 

I've tried setting `margin`, `grid-gutter` and `padding` to zero in the TCSS file, with no success: 

```
DataTable {
	grid-gutter: 0;
	padding: 0;
	margin: 0;
} 
```


Discussion:
We found the following entries in the [FAQ](https://textual.textualize.io/FAQ/) which you may find helpful:


- [How do I center a widget in a screen?](https://textual.textualize.io/FAQ/#how-do-i-center-a-widget-in-a-screen)
- [Why doesn't Textual look good on macOS?](https://textual.textualize.io/FAQ/#why-doesn't-textual-look-good-on-macos)

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by [FAQtory](https://github.com/willmcgugan/faqtory)
I assume you mean the cell padding that's baked in https://github.com/Textualize/textual/discussions/2503#discussioncomment-5822737. 

Textual CSS works at the widget level, but datatable cells aren't widgets internally. Your CSS above is targetting the margin/padding of the table widget itself, not the cells.
@rmurray2 There's no way to remove the cell padding at the moment. Without cell padding the cells would be right next to each other, and it would be difficult to read.

Perhaps you could explain why you want this? We might be able to offer alternative suggestions.
@willmcgugan thanks for your reply. 

I'd ultimately like to generate a heatmap, like the image below, in which the backgrounds correspond to an intensity value for that row/column combination

![heatmap](https://github.com/Textualize/textual/assets/5521035/0c9a6411-49b0-4b27-bfbb-fa467bc807fd)

That's a novel use for the datatable! I doubt it will be difficult for use to add an option to remove cell padding. We will look in to it for the next version.
Great, thanks, just one suggestion: The column header row will become difficult to read if the padding is removed, so I would suggest, if it's possible, tilting the right side of the labels at about a 45 or more degree angle. Or any arbitrary, user-defined angle. That way they won't run into each other.
I may not have followed what you are suggesting there, but its impossible to rotate text in the terminal I'm afraid.

Resolving patch:
diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py
--- a/src/textual/widgets/_data_table.py
+++ b/src/textual/widgets/_data_table.py
@@ -41,7 +41,8 @@
 """The valid types of cursors for [`DataTable.cursor_type`][textual.widgets.DataTable.cursor_type]."""
 CellType = TypeVar("CellType")
 
-CELL_X_PADDING = 2
+_DEFAULT_CELL_X_PADDING = 1
+"""Default padding to use on each side of a column in the data table."""
 
 
 class CellDoesNotExist(Exception):
@@ -170,14 +171,18 @@ class Column:
     content_width: int = 0
     auto_width: bool = False
 
-    @property
-    def render_width(self) -> int:
-        """Width in cells, required to render a column."""
-        # +2 is to account for space padding either side of the cell
-        if self.auto_width:
-            return self.content_width + CELL_X_PADDING
-        else:
-            return self.width + CELL_X_PADDING
+    def get_render_width(self, data_table: DataTable[Any]) -> int:
+        """Width, in cells, required to render the column with padding included.
+
+        Args:
+            data_table: The data table where the column will be rendered.
+
+        Returns:
+            The width, in cells, required to render the column with padding included.
+        """
+        return 2 * data_table.cell_padding + (
+            self.content_width if self.auto_width else self.width
+        )
 
 
 @dataclass
@@ -309,6 +314,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
     show_cursor = Reactive(True)
     cursor_type: Reactive[CursorType] = Reactive[CursorType]("cell")
     """The type of the cursor of the `DataTable`."""
+    cell_padding = Reactive(_DEFAULT_CELL_X_PADDING)
+    """Horizontal padding between cells, applied on each side of each cell."""
 
     cursor_coordinate: Reactive[Coordinate] = Reactive(
         Coordinate(0, 0), repaint=False, always_update=True
@@ -323,6 +330,9 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
     )
     """The coordinate of the `DataTable` that is being hovered."""
 
+    def watch_cell_padding(self) -> None:
+        self._clear_caches()
+
     class CellHighlighted(Message):
         """Posted when the cursor moves to highlight a new cell.
 
@@ -584,11 +594,43 @@ def __init__(
         cursor_foreground_priority: Literal["renderable", "css"] = "css",
         cursor_background_priority: Literal["renderable", "css"] = "renderable",
         cursor_type: CursorType = "cell",
+        cell_padding: int = _DEFAULT_CELL_X_PADDING,
         name: str | None = None,
         id: str | None = None,
         classes: str | None = None,
         disabled: bool = False,
     ) -> None:
+        """Initialises a widget to display tabular data.
+
+        Args:
+            show_header: Whether the table header should be visible or not.
+            show_row_labels: Whether the row labels should be shown or not.
+            fixed_rows: The number of rows, counting from the top, that should be fixed
+                and still visible when the user scrolls down.
+            fixed_columns: The number of columns, counting from the left, that should be
+                fixed and still visible when the user scrolls right.
+            zebra_stripes: Enables or disables a zebra effect applied to the background
+                color of the rows of the table, where alternate colors are styled
+                differently to improve the readability of the table.
+            header_height: The height, in number of cells, of the data table header.
+            show_cursor: Whether the cursor should be visible when navigating the data
+                table or not.
+            cursor_foreground_priority: If the data associated with a cell is an
+                arbitrary renderable with a set foreground color, this determines whether
+                that color is prioritised over the cursor component class or not.
+            cursor_background_priority: If the data associated with a cell is an
+                arbitrary renderable with a set background color, this determines whether
+                that color is prioritesed over the cursor component class or not.
+            cursor_type: The type of cursor to be used when navigating the data table
+                with the keyboard.
+            cell_padding: The number of cells added on each side of each column. Setting
+                this value to zero will likely make your table very heard to read.
+            name: The name of the widget.
+            id: The ID of the widget in the DOM.
+            classes: The CSS classes for the widget.
+            disabled: Whether the widget is disabled or not.
+        """
+
         super().__init__(name=name, id=id, classes=classes, disabled=disabled)
         self._data: dict[RowKey, dict[ColumnKey, CellType]] = {}
         """Contains the cells of the table, indexed by row key and column key.
@@ -673,6 +715,8 @@ def __init__(
          in the event where a cell contains a renderable with a background color."""
         self.cursor_type = cursor_type
         """The type of cursor of the `DataTable`."""
+        self.cell_padding = cell_padding
+        """Horizontal padding between cells, applied on each side of each cell."""
 
     @property
     def hover_row(self) -> int:
@@ -996,7 +1040,7 @@ def watch_show_header(self, show: bool) -> None:
 
     def watch_show_row_labels(self, show: bool) -> None:
         width, height = self.virtual_size
-        column_width = self._label_column.render_width
+        column_width = self._label_column.get_render_width(self)
         width_change = column_width if show else -column_width
         self.virtual_size = Size(width + width_change, height)
         self._scroll_cursor_into_view()
@@ -1011,6 +1055,19 @@ def watch_fixed_columns(self) -> None:
     def watch_zebra_stripes(self) -> None:
         self._clear_caches()
 
+    def validate_cell_padding(self, cell_padding: int) -> int:
+        return max(cell_padding, 0)
+
+    def watch_cell_padding(self, old_padding: int, new_padding: int) -> None:
+        # A single side of a single cell will have its width changed by (new - old),
+        # so the total width change is double that per column, times the number of
+        # columns for the whole data table.
+        width_change = 2 * (new_padding - old_padding) * len(self.columns)
+        width, height = self.virtual_size
+        self.virtual_size = Size(width + width_change, height)
+        self._scroll_cursor_into_view()
+        self._clear_caches()
+
     def watch_hover_coordinate(self, old: Coordinate, value: Coordinate) -> None:
         self.refresh_coordinate(old)
         self.refresh_coordinate(value)
@@ -1164,7 +1221,11 @@ def _highlight_cursor(self) -> None:
     @property
     def _row_label_column_width(self) -> int:
         """The render width of the column containing row labels"""
-        return self._label_column.render_width if self._should_render_row_labels else 0
+        return (
+            self._label_column.get_render_width(self)
+            if self._should_render_row_labels
+            else 0
+        )
 
     def _update_column_widths(self, updated_cells: set[CellKey]) -> None:
         """Update the widths of the columns based on the newly updated cell widths."""
@@ -1259,7 +1320,7 @@ def _update_dimensions(self, new_rows: Iterable[RowKey]) -> None:
                         row_index,
                         column_index,
                         style,
-                        column.render_width,
+                        column.get_render_width(self),
                         cursor=should_highlight(
                             cursor_location, cell_location, cursor_type
                         ),
@@ -1269,7 +1330,7 @@ def _update_dimensions(self, new_rows: Iterable[RowKey]) -> None:
                     )
                     cell_height = len(rendered_cell)
                     rendered_cells.append(
-                        (rendered_cell, cell_height, column.render_width)
+                        (rendered_cell, cell_height, column.get_render_width(self))
                     )
                     height = max(height, cell_height)
 
@@ -1286,7 +1347,9 @@ def _update_dimensions(self, new_rows: Iterable[RowKey]) -> None:
                             ]
                         )
 
-        data_cells_width = sum(column.render_width for column in self.columns.values())
+        data_cells_width = sum(
+            column.get_render_width(self) for column in self.columns.values()
+        )
         total_width = data_cells_width + self._row_label_column_width
         header_height = self.header_height if self.show_header else 0
         self.virtual_size = Size(
@@ -1306,11 +1369,14 @@ def _get_cell_region(self, coordinate: Coordinate) -> Region:
         # The x-coordinate of a cell is the sum of widths of the data cells to the left
         # plus the width of the render width of the longest row label.
         x = (
-            sum(column.render_width for column in self.ordered_columns[:column_index])
+            sum(
+                column.get_render_width(self)
+                for column in self.ordered_columns[:column_index]
+            )
             + self._row_label_column_width
         )
         column_key = self._column_locations.get_key(column_index)
-        width = self.columns[column_key].render_width
+        width = self.columns[column_key].get_render_width(self)
         height = row.height
         y = sum(ordered_row.height for ordered_row in self.ordered_rows[:row_index])
         if self.show_header:
@@ -1327,7 +1393,7 @@ def _get_row_region(self, row_index: int) -> Region:
         row_key = self._row_locations.get_key(row_index)
         row = rows[row_key]
         row_width = (
-            sum(column.render_width for column in self.columns.values())
+            sum(column.get_render_width(self) for column in self.columns.values())
             + self._row_label_column_width
         )
         y = sum(ordered_row.height for ordered_row in self.ordered_rows[:row_index])
@@ -1343,11 +1409,14 @@ def _get_column_region(self, column_index: int) -> Region:
 
         columns = self.columns
         x = (
-            sum(column.render_width for column in self.ordered_columns[:column_index])
+            sum(
+                column.get_render_width(self)
+                for column in self.ordered_columns[:column_index]
+            )
             + self._row_label_column_width
         )
         column_key = self._column_locations.get_key(column_index)
-        width = columns[column_key].render_width
+        width = columns[column_key].get_render_width(self)
         header_height = self.header_height if self.show_header else 0
         height = self._total_row_height + header_height
         full_column_region = Region(x, 0, width, height)
@@ -1881,7 +1950,7 @@ def _render_cell(
                     )
             lines = self.app.console.render_lines(
                 Styled(
-                    Padding(cell, (0, 1)),
+                    Padding(cell, (0, self.cell_padding)),
                     pre_style=base_style + component_style,
                     post_style=post_style,
                 ),
@@ -2030,7 +2099,7 @@ def _render_line_in_row(
                     row_index,
                     column_index,
                     fixed_style,
-                    column.render_width,
+                    column.get_render_width(self),
                     cursor=should_highlight(
                         cursor_location, cell_location, cursor_type
                     ),
@@ -2047,7 +2116,7 @@ def _render_line_in_row(
                 row_index,
                 column_index,
                 row_style,
-                column.render_width,
+                column.get_render_width(self),
                 cursor=should_highlight(cursor_location, cell_location, cursor_type),
                 hover=should_highlight(hover_location, cell_location, cursor_type),
             )[line_no]
@@ -2057,7 +2126,7 @@ def _render_line_in_row(
         widget_width = self.size.width
         table_width = (
             sum(
-                column.render_width
+                column.get_render_width(self)
                 for column in self.ordered_columns[self.fixed_columns :]
             )
             + self._row_label_column_width
@@ -2141,7 +2210,8 @@ def _render_line(self, y: int, x1: int, x2: int, base_style: Style) -> Strip:
             hover_location=self.hover_coordinate,
         )
         fixed_width = sum(
-            column.render_width for column in self.ordered_columns[: self.fixed_columns]
+            column.get_render_width(self)
+            for column in self.ordered_columns[: self.fixed_columns]
         )
 
         fixed_line: list[Segment] = list(chain.from_iterable(fixed)) if fixed else []
@@ -2260,7 +2330,7 @@ def _get_fixed_offset(self) -> Spacing:
         top += sum(row.height for row in self.ordered_rows[: self.fixed_rows])
         left = (
             sum(
-                column.render_width
+                column.get_render_width(self)
                 for column in self.ordered_columns[: self.fixed_columns]
             )
             + self._row_label_column_width


Test patch:
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -14001,6 +14001,322 @@
   
   '''
 # ---
+# name: test_datatable_cell_padding
+  '''
+  <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
+      <!-- Generated with Rich https://www.textualize.io -->
+      <style>
+  
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Regular"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+          font-style: normal;
+          font-weight: 400;
+      }
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Bold"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+          font-style: bold;
+          font-weight: 700;
+      }
+  
+      .terminal-1699433504-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-1699433504-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-1699433504-r1 { fill: #e1e1e1 }
+  .terminal-1699433504-r2 { fill: #c5c8c6 }
+  .terminal-1699433504-r3 { fill: #dde6ed;font-weight: bold }
+  .terminal-1699433504-r4 { fill: #dde6ed }
+  .terminal-1699433504-r5 { fill: #211505 }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-1699433504-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-1699433504-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-1699433504-line-22">
+      <rect x="0" y="538.3" width="976" height="24.65"/>
+              </clipPath>
+      </defs>
+  
+      <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-1699433504-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">TableApp</text>
+              <g transform="translate(26,22)">
+              <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+              <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+              <circle cx="44" cy="0" r="7" fill="#28c840"/>
+              </g>
+          
+      <g transform="translate(9, 41)" clip-path="url(#terminal-1699433504-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="73.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="134.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="195.2" y="25.9" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="134.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="50.3" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="97.6" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="183" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="268.4" y="99.1" width="695.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="97.6" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="183" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="268.4" y="123.5" width="695.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="122" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="231.8" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="341.6" y="172.3" width="622.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="196.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="122" y="196.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="231.8" y="196.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="341.6" y="196.7" width="622.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="245.5" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="146.4" y="245.5" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="280.6" y="245.5" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="414.8" y="245.5" width="549" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="146.4" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="269.9" width="549" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="318.7" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="170.8" y="318.7" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="329.4" y="318.7" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="488" y="318.7" width="475.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="343.1" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="170.8" y="343.1" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="329.4" y="343.1" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="343.1" width="475.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-1699433504-matrix">
+      <text class="terminal-1699433504-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-1699433504-line-0)">
+  </text><text class="terminal-1699433504-r3" x="12.2" y="44.4" textLength="61" clip-path="url(#terminal-1699433504-line-1)">one&#160;&#160;</text><text class="terminal-1699433504-r3" x="73.2" y="44.4" textLength="61" clip-path="url(#terminal-1699433504-line-1)">two&#160;&#160;</text><text class="terminal-1699433504-r3" x="134.2" y="44.4" textLength="61" clip-path="url(#terminal-1699433504-line-1)">three</text><text class="terminal-1699433504-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-1699433504-line-1)">
+  </text><text class="terminal-1699433504-r5" x="12.2" y="68.8" textLength="61" clip-path="url(#terminal-1699433504-line-2)">value</text><text class="terminal-1699433504-r1" x="73.2" y="68.8" textLength="61" clip-path="url(#terminal-1699433504-line-2)">value</text><text class="terminal-1699433504-r1" x="134.2" y="68.8" textLength="61" clip-path="url(#terminal-1699433504-line-2)">val&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-1699433504-line-2)">
+  </text><text class="terminal-1699433504-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-1699433504-line-3)">
+  </text><text class="terminal-1699433504-r3" x="12.2" y="117.6" textLength="85.4" clip-path="url(#terminal-1699433504-line-4)">&#160;one&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="97.6" y="117.6" textLength="85.4" clip-path="url(#terminal-1699433504-line-4)">&#160;two&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="183" y="117.6" textLength="85.4" clip-path="url(#terminal-1699433504-line-4)">&#160;three&#160;</text><text class="terminal-1699433504-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-1699433504-line-4)">
+  </text><text class="terminal-1699433504-r5" x="12.2" y="142" textLength="85.4" clip-path="url(#terminal-1699433504-line-5)">&#160;value&#160;</text><text class="terminal-1699433504-r1" x="97.6" y="142" textLength="85.4" clip-path="url(#terminal-1699433504-line-5)">&#160;value&#160;</text><text class="terminal-1699433504-r1" x="183" y="142" textLength="85.4" clip-path="url(#terminal-1699433504-line-5)">&#160;val&#160;&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-1699433504-line-5)">
+  </text><text class="terminal-1699433504-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-1699433504-line-6)">
+  </text><text class="terminal-1699433504-r3" x="12.2" y="190.8" textLength="109.8" clip-path="url(#terminal-1699433504-line-7)">&#160;&#160;one&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="122" y="190.8" textLength="109.8" clip-path="url(#terminal-1699433504-line-7)">&#160;&#160;two&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="231.8" y="190.8" textLength="109.8" clip-path="url(#terminal-1699433504-line-7)">&#160;&#160;three&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-1699433504-line-7)">
+  </text><text class="terminal-1699433504-r5" x="12.2" y="215.2" textLength="109.8" clip-path="url(#terminal-1699433504-line-8)">&#160;&#160;value&#160;&#160;</text><text class="terminal-1699433504-r1" x="122" y="215.2" textLength="109.8" clip-path="url(#terminal-1699433504-line-8)">&#160;&#160;value&#160;&#160;</text><text class="terminal-1699433504-r1" x="231.8" y="215.2" textLength="109.8" clip-path="url(#terminal-1699433504-line-8)">&#160;&#160;val&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-1699433504-line-8)">
+  </text><text class="terminal-1699433504-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-1699433504-line-9)">
+  </text><text class="terminal-1699433504-r3" x="12.2" y="264" textLength="134.2" clip-path="url(#terminal-1699433504-line-10)">&#160;&#160;&#160;one&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="146.4" y="264" textLength="134.2" clip-path="url(#terminal-1699433504-line-10)">&#160;&#160;&#160;two&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="280.6" y="264" textLength="134.2" clip-path="url(#terminal-1699433504-line-10)">&#160;&#160;&#160;three&#160;&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-1699433504-line-10)">
+  </text><text class="terminal-1699433504-r5" x="12.2" y="288.4" textLength="134.2" clip-path="url(#terminal-1699433504-line-11)">&#160;&#160;&#160;value&#160;&#160;&#160;</text><text class="terminal-1699433504-r1" x="146.4" y="288.4" textLength="134.2" clip-path="url(#terminal-1699433504-line-11)">&#160;&#160;&#160;value&#160;&#160;&#160;</text><text class="terminal-1699433504-r1" x="280.6" y="288.4" textLength="134.2" clip-path="url(#terminal-1699433504-line-11)">&#160;&#160;&#160;val&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-1699433504-line-11)">
+  </text><text class="terminal-1699433504-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-1699433504-line-12)">
+  </text><text class="terminal-1699433504-r3" x="12.2" y="337.2" textLength="158.6" clip-path="url(#terminal-1699433504-line-13)">&#160;&#160;&#160;&#160;one&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="170.8" y="337.2" textLength="158.6" clip-path="url(#terminal-1699433504-line-13)">&#160;&#160;&#160;&#160;two&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r3" x="329.4" y="337.2" textLength="158.6" clip-path="url(#terminal-1699433504-line-13)">&#160;&#160;&#160;&#160;three&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-1699433504-line-13)">
+  </text><text class="terminal-1699433504-r5" x="12.2" y="361.6" textLength="158.6" clip-path="url(#terminal-1699433504-line-14)">&#160;&#160;&#160;&#160;value&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r1" x="170.8" y="361.6" textLength="158.6" clip-path="url(#terminal-1699433504-line-14)">&#160;&#160;&#160;&#160;value&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r1" x="329.4" y="361.6" textLength="158.6" clip-path="url(#terminal-1699433504-line-14)">&#160;&#160;&#160;&#160;val&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-1699433504-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-1699433504-line-14)">
+  </text><text class="terminal-1699433504-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-1699433504-line-15)">
+  </text><text class="terminal-1699433504-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-1699433504-line-16)">
+  </text><text class="terminal-1699433504-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-1699433504-line-17)">
+  </text><text class="terminal-1699433504-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-1699433504-line-18)">
+  </text><text class="terminal-1699433504-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-1699433504-line-19)">
+  </text><text class="terminal-1699433504-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-1699433504-line-20)">
+  </text><text class="terminal-1699433504-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-1699433504-line-21)">
+  </text><text class="terminal-1699433504-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-1699433504-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
+# name: test_datatable_change_cell_padding
+  '''
+  <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
+      <!-- Generated with Rich https://www.textualize.io -->
+      <style>
+  
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Regular"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+          font-style: normal;
+          font-weight: 400;
+      }
+      @font-face {
+          font-family: "Fira Code";
+          src: local("FiraCode-Bold"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+                  url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+          font-style: bold;
+          font-weight: 700;
+      }
+  
+      .terminal-236473376-matrix {
+          font-family: Fira Code, monospace;
+          font-size: 20px;
+          line-height: 24.4px;
+          font-variant-east-asian: full-width;
+      }
+  
+      .terminal-236473376-title {
+          font-size: 18px;
+          font-weight: bold;
+          font-family: arial;
+      }
+  
+      .terminal-236473376-r1 { fill: #e1e1e1 }
+  .terminal-236473376-r2 { fill: #c5c8c6 }
+  .terminal-236473376-r3 { fill: #dde6ed;font-weight: bold }
+  .terminal-236473376-r4 { fill: #dde6ed }
+  .terminal-236473376-r5 { fill: #211505 }
+      </style>
+  
+      <defs>
+      <clipPath id="terminal-236473376-clip-terminal">
+        <rect x="0" y="0" width="975.0" height="584.5999999999999" />
+      </clipPath>
+      <clipPath id="terminal-236473376-line-0">
+      <rect x="0" y="1.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-1">
+      <rect x="0" y="25.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-2">
+      <rect x="0" y="50.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-3">
+      <rect x="0" y="74.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-4">
+      <rect x="0" y="99.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-5">
+      <rect x="0" y="123.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-6">
+      <rect x="0" y="147.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-7">
+      <rect x="0" y="172.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-8">
+      <rect x="0" y="196.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-9">
+      <rect x="0" y="221.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-10">
+      <rect x="0" y="245.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-11">
+      <rect x="0" y="269.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-12">
+      <rect x="0" y="294.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-13">
+      <rect x="0" y="318.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-14">
+      <rect x="0" y="343.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-15">
+      <rect x="0" y="367.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-16">
+      <rect x="0" y="391.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-17">
+      <rect x="0" y="416.3" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-18">
+      <rect x="0" y="440.7" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-19">
+      <rect x="0" y="465.1" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-20">
+      <rect x="0" y="489.5" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-21">
+      <rect x="0" y="513.9" width="976" height="24.65"/>
+              </clipPath>
+  <clipPath id="terminal-236473376-line-22">
+      <rect x="0" y="538.3" width="976" height="24.65"/>
+              </clipPath>
+      </defs>
+  
+      <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-236473376-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">TableApp</text>
+              <g transform="translate(26,22)">
+              <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+              <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+              <circle cx="44" cy="0" r="7" fill="#28c840"/>
+              </g>
+          
+      <g transform="translate(9, 41)" clip-path="url(#terminal-236473376-clip-terminal)">
+      <rect fill="#1e1e1e" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="73.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="134.2" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="195.2" y="25.9" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="73.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="134.2" y="50.3" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="195.2" y="50.3" width="768.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="97.6" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="183" y="99.1" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="268.4" y="99.1" width="695.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="97.6" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="183" y="123.5" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="268.4" y="123.5" width="695.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="122" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="231.8" y="172.3" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="341.6" y="172.3" width="622.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="196.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="122" y="196.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="231.8" y="196.7" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="341.6" y="196.7" width="622.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="245.5" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="146.4" y="245.5" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="280.6" y="245.5" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="414.8" y="245.5" width="549" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="146.4" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="280.6" y="269.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="414.8" y="269.9" width="549" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="12.2" y="318.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="317.2" y="318.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#004578" x="622.2" y="318.7" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#073b61" x="927.2" y="318.7" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#fea62b" x="12.2" y="343.1" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="317.2" y="343.1" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="622.2" y="343.1" width="305" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="927.2" y="343.1" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="963.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
+      <g class="terminal-236473376-matrix">
+      <text class="terminal-236473376-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-236473376-line-0)">
+  </text><text class="terminal-236473376-r3" x="12.2" y="44.4" textLength="61" clip-path="url(#terminal-236473376-line-1)">one&#160;&#160;</text><text class="terminal-236473376-r3" x="73.2" y="44.4" textLength="61" clip-path="url(#terminal-236473376-line-1)">two&#160;&#160;</text><text class="terminal-236473376-r3" x="134.2" y="44.4" textLength="61" clip-path="url(#terminal-236473376-line-1)">three</text><text class="terminal-236473376-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-236473376-line-1)">
+  </text><text class="terminal-236473376-r5" x="12.2" y="68.8" textLength="61" clip-path="url(#terminal-236473376-line-2)">value</text><text class="terminal-236473376-r1" x="73.2" y="68.8" textLength="61" clip-path="url(#terminal-236473376-line-2)">value</text><text class="terminal-236473376-r1" x="134.2" y="68.8" textLength="61" clip-path="url(#terminal-236473376-line-2)">val&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-236473376-line-2)">
+  </text><text class="terminal-236473376-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-236473376-line-3)">
+  </text><text class="terminal-236473376-r3" x="12.2" y="117.6" textLength="85.4" clip-path="url(#terminal-236473376-line-4)">&#160;one&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="97.6" y="117.6" textLength="85.4" clip-path="url(#terminal-236473376-line-4)">&#160;two&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="183" y="117.6" textLength="85.4" clip-path="url(#terminal-236473376-line-4)">&#160;three&#160;</text><text class="terminal-236473376-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-236473376-line-4)">
+  </text><text class="terminal-236473376-r5" x="12.2" y="142" textLength="85.4" clip-path="url(#terminal-236473376-line-5)">&#160;value&#160;</text><text class="terminal-236473376-r1" x="97.6" y="142" textLength="85.4" clip-path="url(#terminal-236473376-line-5)">&#160;value&#160;</text><text class="terminal-236473376-r1" x="183" y="142" textLength="85.4" clip-path="url(#terminal-236473376-line-5)">&#160;val&#160;&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-236473376-line-5)">
+  </text><text class="terminal-236473376-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-236473376-line-6)">
+  </text><text class="terminal-236473376-r3" x="12.2" y="190.8" textLength="109.8" clip-path="url(#terminal-236473376-line-7)">&#160;&#160;one&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="122" y="190.8" textLength="109.8" clip-path="url(#terminal-236473376-line-7)">&#160;&#160;two&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="231.8" y="190.8" textLength="109.8" clip-path="url(#terminal-236473376-line-7)">&#160;&#160;three&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-236473376-line-7)">
+  </text><text class="terminal-236473376-r5" x="12.2" y="215.2" textLength="109.8" clip-path="url(#terminal-236473376-line-8)">&#160;&#160;value&#160;&#160;</text><text class="terminal-236473376-r1" x="122" y="215.2" textLength="109.8" clip-path="url(#terminal-236473376-line-8)">&#160;&#160;value&#160;&#160;</text><text class="terminal-236473376-r1" x="231.8" y="215.2" textLength="109.8" clip-path="url(#terminal-236473376-line-8)">&#160;&#160;val&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-236473376-line-8)">
+  </text><text class="terminal-236473376-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-236473376-line-9)">
+  </text><text class="terminal-236473376-r3" x="12.2" y="264" textLength="134.2" clip-path="url(#terminal-236473376-line-10)">&#160;&#160;&#160;one&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="146.4" y="264" textLength="134.2" clip-path="url(#terminal-236473376-line-10)">&#160;&#160;&#160;two&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="280.6" y="264" textLength="134.2" clip-path="url(#terminal-236473376-line-10)">&#160;&#160;&#160;three&#160;&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-236473376-line-10)">
+  </text><text class="terminal-236473376-r5" x="12.2" y="288.4" textLength="134.2" clip-path="url(#terminal-236473376-line-11)">&#160;&#160;&#160;value&#160;&#160;&#160;</text><text class="terminal-236473376-r1" x="146.4" y="288.4" textLength="134.2" clip-path="url(#terminal-236473376-line-11)">&#160;&#160;&#160;value&#160;&#160;&#160;</text><text class="terminal-236473376-r1" x="280.6" y="288.4" textLength="134.2" clip-path="url(#terminal-236473376-line-11)">&#160;&#160;&#160;val&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-236473376-line-11)">
+  </text><text class="terminal-236473376-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-236473376-line-12)">
+  </text><text class="terminal-236473376-r3" x="12.2" y="337.2" textLength="305" clip-path="url(#terminal-236473376-line-13)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;one&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="317.2" y="337.2" textLength="305" clip-path="url(#terminal-236473376-line-13)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;two&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r3" x="622.2" y="337.2" textLength="305" clip-path="url(#terminal-236473376-line-13)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;three&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-236473376-line-13)">
+  </text><text class="terminal-236473376-r5" x="12.2" y="361.6" textLength="305" clip-path="url(#terminal-236473376-line-14)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;value&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r1" x="317.2" y="361.6" textLength="305" clip-path="url(#terminal-236473376-line-14)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;value&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r1" x="622.2" y="361.6" textLength="305" clip-path="url(#terminal-236473376-line-14)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;val&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-236473376-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-236473376-line-14)">
+  </text><text class="terminal-236473376-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-236473376-line-15)">
+  </text><text class="terminal-236473376-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-236473376-line-16)">
+  </text><text class="terminal-236473376-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-236473376-line-17)">
+  </text><text class="terminal-236473376-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-236473376-line-18)">
+  </text><text class="terminal-236473376-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-236473376-line-19)">
+  </text><text class="terminal-236473376-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-236473376-line-20)">
+  </text><text class="terminal-236473376-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-236473376-line-21)">
+  </text><text class="terminal-236473376-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-236473376-line-22)">
+  </text>
+      </g>
+      </g>
+  </svg>
+  
+  '''
+# ---
 # name: test_datatable_column_cursor_render
   '''
   <svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
diff --git a/tests/snapshot_tests/snapshot_apps/data_table_cell_padding.py b/tests/snapshot_tests/snapshot_apps/data_table_cell_padding.py
new file mode 100644
--- /dev/null
+++ b/tests/snapshot_tests/snapshot_apps/data_table_cell_padding.py
@@ -0,0 +1,28 @@
+from textual.app import App, ComposeResult
+from textual.widgets import DataTable
+
+
+class TableApp(App):
+    CSS = """
+    DataTable {
+        margin: 1;
+    }
+    """
+
+    def compose(self) -> ComposeResult:
+        for cell_padding in range(5):
+            dt = DataTable(cell_padding=cell_padding)
+            dt.add_columns("one", "two", "three")
+            dt.add_row("value", "value", "val")
+            yield dt
+
+    def key_a(self):
+        self.query(DataTable).last().cell_padding = 20
+
+    def key_b(self):
+        self.query(DataTable).last().cell_padding = 10
+
+
+app = TableApp()
+if __name__ == "__main__":
+    app.run()
diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py
--- a/tests/snapshot_tests/test_snapshots.py
+++ b/tests/snapshot_tests/test_snapshots.py
@@ -167,6 +167,18 @@ def test_datatable_add_row_auto_height_sorted(snap_compare):
     )
 
 
+def test_datatable_cell_padding(snap_compare):
+    # Check that horizontal cell padding is respected.
+    assert snap_compare(SNAPSHOT_APPS_DIR / "data_table_cell_padding.py")
+
+
+def test_datatable_change_cell_padding(snap_compare):
+    # Check that horizontal cell padding is respected.
+    assert snap_compare(
+        SNAPSHOT_APPS_DIR / "data_table_cell_padding.py", press=["a", "b"]
+    )
+
+
 def test_footer_render(snap_compare):
     assert snap_compare(WIDGET_EXAMPLES_DIR / "footer.py")
 
@@ -708,7 +720,9 @@ def test_nested_fr(snap_compare) -> None:
     assert snap_compare(SNAPSHOT_APPS_DIR / "nested_fr.py")
 
 
-@pytest.mark.skipif(sys.version_info < (3, 8), reason="tree-sitter requires python3.8 or higher")
+@pytest.mark.skipif(
+    sys.version_info < (3, 8), reason="tree-sitter requires python3.8 or higher"
+)
 @pytest.mark.parametrize("language", BUILTIN_LANGUAGES)
 def test_text_area_language_rendering(language, snap_compare):
     # This test will fail if we're missing a snapshot test for a valid
@@ -760,9 +774,12 @@ def setup_selection(pilot):
     )
 
 
-@pytest.mark.skipif(sys.version_info < (3, 8), reason="tree-sitter requires python3.8 or higher")
-@pytest.mark.parametrize("theme_name",
-                         [theme.name for theme in TextAreaTheme.builtin_themes()])
+@pytest.mark.skipif(
+    sys.version_info < (3, 8), reason="tree-sitter requires python3.8 or higher"
+)
+@pytest.mark.parametrize(
+    "theme_name", [theme.name for theme in TextAreaTheme.builtin_themes()]
+)
 def test_text_area_themes(snap_compare, theme_name):
     """Each theme should have its own snapshot with at least some Python
     to check that the rendering is sensible. This also ensures that theme
diff --git a/tests/test_data_table.py b/tests/test_data_table.py
--- a/tests/test_data_table.py
+++ b/tests/test_data_table.py
@@ -11,6 +11,7 @@
 from textual.geometry import Offset
 from textual.message import Message
 from textual.widgets import DataTable
+from textual.widgets._data_table import _DEFAULT_CELL_X_PADDING
 from textual.widgets.data_table import (
     CellDoesNotExist,
     CellKey,
@@ -271,7 +272,10 @@ async def test_add_column_with_width():
         row = table.add_row("123")
         assert table.get_cell(row, column) == "123"
         assert table.columns[column].width == 10
-        assert table.columns[column].render_width == 12  # 10 + (2 padding)
+        assert (
+            table.columns[column].get_render_width(table)
+            == 10 + 2 * _DEFAULT_CELL_X_PADDING
+        )
 
 
 async def test_add_columns():
@@ -689,7 +693,10 @@ async def test_update_cell_at_column_width(label, new_value, new_content_width):
         table.update_cell_at(Coordinate(0, 0), new_value, update_width=True)
         await wait_for_idle()
         assert first_column.content_width == new_content_width
-        assert first_column.render_width == new_content_width + 2
+        assert (
+            first_column.get_render_width(table)
+            == new_content_width + 2 * _DEFAULT_CELL_X_PADDING
+        )
 
 
 async def test_coordinate_to_cell_key():
@@ -1188,17 +1195,59 @@ async def test_add_row_auto_height(cell: RenderableType, height: int):
 async def test_add_row_expands_column_widths():
     """Regression test for https://github.com/Textualize/textual/issues/1026."""
     app = DataTableApp()
-    from textual.widgets._data_table import CELL_X_PADDING
 
     async with app.run_test() as pilot:
         table = app.query_one(DataTable)
         table.add_column("First")
         table.add_column("Second", width=10)
         await pilot.pause()
-        assert table.ordered_columns[0].render_width == 5 + CELL_X_PADDING
-        assert table.ordered_columns[1].render_width == 10 + CELL_X_PADDING
+        assert (
+            table.ordered_columns[0].get_render_width(table)
+            == 5 + 2 * _DEFAULT_CELL_X_PADDING
+        )
+        assert (
+            table.ordered_columns[1].get_render_width(table)
+            == 10 + 2 * _DEFAULT_CELL_X_PADDING
+        )
 
         table.add_row("a" * 20, "a" * 20)
         await pilot.pause()
-        assert table.ordered_columns[0].render_width == 20 + CELL_X_PADDING
-        assert table.ordered_columns[1].render_width == 10 + CELL_X_PADDING
+        assert (
+            table.ordered_columns[0].get_render_width(table)
+            == 20 + 2 * _DEFAULT_CELL_X_PADDING
+        )
+        assert (
+            table.ordered_columns[1].get_render_width(table)
+            == 10 + 2 * _DEFAULT_CELL_X_PADDING
+        )
+
+
+async def test_cell_padding_updates_virtual_size():
+    app = DataTableApp()
+
+    async with app.run_test() as pilot:
+        table = app.query_one(DataTable)
+        table.add_column("First")
+        table.add_column("Second", width=10)
+        table.add_column("Third")
+
+        width = table.virtual_size.width
+
+        table.cell_padding += 5
+        assert width + 5 * 2 * 3 == table.virtual_size.width
+
+        table.cell_padding -= 2
+        assert width + 3 * 2 * 3 == table.virtual_size.width
+
+        table.cell_padding += 10
+        assert width + 13 * 2 * 3 == table.virtual_size.width
+
+
+async def test_cell_padding_cannot_be_negative():
+    app = DataTableApp()
+    async with app.run_test():
+        table = app.query_one(DataTable)
+        table.cell_padding = -3
+        assert table.cell_padding == 0
+        table.cell_padding = -1234
+        assert table.cell_padding == 0