diff --git a/frontend/src/metabase-shared/color_selector.js b/frontend/src/metabase-shared/color_selector.js
index f87318bd4b1cd9b017a346743794b5c0575a3da0..dc51bf32b577881030ba14dbdeacccda718f9d3c 100644
--- a/frontend/src/metabase-shared/color_selector.js
+++ b/frontend/src/metabase-shared/color_selector.js
@@ -21,7 +21,12 @@ global.makeCellBackgroundGetter = function (
   const cols = JSON.parse(colsJSON);
   const settings = JSON.parse(settingsJSON);
   try {
-    return makeCellBackgroundGetter(rows, cols, settings);
+    return makeCellBackgroundGetter(
+      rows,
+      cols,
+      settings["table.column_formatting"] ?? [],
+      settings["table.pivot"],
+    );
   } catch (e) {
     print("ERROR", e);
     return () => null;
diff --git a/frontend/src/metabase/lib/data_grid.js b/frontend/src/metabase/lib/data_grid.js
index 575d3853aec57b7298223009a9c588ff8d6b3d35..1cd177fe217a833dfb4b125cc27abaa12dfb7cd3 100644
--- a/frontend/src/metabase/lib/data_grid.js
+++ b/frontend/src/metabase/lib/data_grid.js
@@ -1,6 +1,7 @@
 import _ from "underscore";
 import { getIn } from "icepick";
 import { t } from "ttag";
+import { makeCellBackgroundGetter } from "metabase/visualizations/lib/table_format";
 
 import { formatValue, formatColumn } from "metabase/lib/formatting";
 
@@ -8,6 +9,7 @@ export function isPivotGroupColumn(col) {
   return col.name === "pivot-grouping";
 }
 
+export const COLUMN_FORMATTING_SETTING = "table.column_formatting";
 export const COLLAPSED_ROWS_SETTING = "pivot_table.collapsed_rows";
 export const COLUMN_SPLIT_SETTING = "pivot_table.column_split";
 export const COLUMN_SHOW_TOTALS = "pivot_table.column_show_totals";
@@ -38,12 +40,7 @@ export function multiLevelPivot(data, settings) {
       .filter(index => index !== -1),
   );
 
-  const { pivotData, columns } = splitPivotData(
-    data,
-    rowColumnIndexes,
-    columnColumnIndexes,
-  );
-
+  const { pivotData, columns } = splitPivotData(data);
   const columnSettings = columns.map(column => settings.column(column));
   const allCollapsedSubtotals = settings[COLLAPSED_ROWS_SETTING].value;
   const collapsedSubtotals = filterCollapsedSubtotals(
@@ -83,8 +80,13 @@ export function multiLevelPivot(data, settings) {
       columnColumnIndexes.concat(rowColumnIndexes).map(index => row[index]),
     );
     const values = valueColumnIndexes.map(index => row[index]);
+    const valueColumns = valueColumnIndexes.map(
+      index => columnSettings[index]?.column,
+    );
+
     valuesByKey[valueKey] = {
       values,
+      valueColumns,
       data: row.map((value, index) => ({ value, col: columns[index] })),
       dimensions: row
         .map((value, index) => ({
@@ -180,6 +182,13 @@ export function multiLevelPivot(data, settings) {
   const leftHeaderItems = treeToArray(formattedRowTree.flat());
   const topHeaderItems = treeToArray(formattedColumnTree.flat());
 
+  const colorGetter = makeCellBackgroundGetter(
+    pivotData[primaryRowsKey],
+    columns,
+    settings["table.column_formatting"] ?? [],
+    true,
+  );
+
   const getRowSection = createRowSectionGetter({
     valuesByKey,
     subtotalValues,
@@ -188,6 +197,7 @@ export function multiLevelPivot(data, settings) {
     rowColumnIndexes,
     columnIndex,
     rowIndex,
+    colorGetter,
   });
 
   return {
@@ -206,7 +216,7 @@ export function multiLevelPivot(data, settings) {
 // This pulls apart the different aggregations that were packed into one result set.
 // There's a column indicating which breakouts were used to compute that row.
 // We use that column to split apart the data and convert the field refs to indexes.
-function splitPivotData(data, rowIndexes, columnIndexes) {
+function splitPivotData(data) {
   const groupIndex = data.cols.findIndex(isPivotGroupColumn);
   const columns = data.cols.filter(col => !isPivotGroupColumn(col));
   const breakouts = columns.filter(col => col.source === "breakout");
@@ -260,6 +270,7 @@ function createRowSectionGetter({
   rowColumnIndexes,
   columnIndex,
   rowIndex,
+  colorGetter,
 }) {
   const formatValues = values =>
     values === undefined
@@ -292,10 +303,20 @@ function createRowSectionGetter({
       const otherAttrs = rowValues.length === 0 ? { isGrandTotal: true } : {};
       return getSubtotals(indexes, indexValues, otherAttrs);
     }
-    const { values, data, dimensions } =
+    const { values, data, dimensions, valueColumns } =
       valuesByKey[JSON.stringify(indexValues)] || {};
-    return formatValues(values).map(o =>
-      data === undefined ? o : { ...o, clicked: { data, dimensions } },
+    return formatValues(values).map((o, index) =>
+      data === undefined
+        ? o
+        : {
+            ...o,
+            clicked: { data, dimensions },
+            backgroundColor: colorGetter(
+              values[index],
+              o.rowIndex,
+              valueColumns[index].name,
+            ),
+          },
     );
   };
   return _.memoize(getter, (i1, i2) => [i1, i2].join());
diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx
index b31a5575e343939d1994d558342dad92ce4be64e..cf15df53506403b00783e7da48f613eca33499e4 100644
--- a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx
+++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx
@@ -94,11 +94,12 @@ export default class ChartSettingsTableFormatting extends React.Component {
     editingRuleIsNew: null,
   };
   render() {
-    const { value, onChange, cols } = this.props;
+    const { value, onChange, cols, canHighlightRow } = this.props;
     const { editingRule, editingRuleIsNew } = this.state;
     if (editingRule !== null && value[editingRule]) {
       return (
         <RuleEditor
+          canHighlightRow={canHighlightRow}
           rule={value[editingRule]}
           cols={cols}
           isNew={editingRuleIsNew}
@@ -297,7 +298,15 @@ const RuleDescription = ({ rule }) => {
   );
 };
 
-const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => {
+const RuleEditor = ({
+  rule,
+  cols,
+  isNew,
+  onChange,
+  onDone,
+  onRemove,
+  canHighlightRow = true,
+}) => {
   const selectedColumns = rule.columns.map(name => _.findWhere(cols, { name }));
   const isStringRule =
     selectedColumns.length > 0 && _.all(selectedColumns, isString);
@@ -363,6 +372,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => {
           </Select>
           {hasOperand && isNumericRule ? (
             <NumericInput
+              data-testid="conditional-formatting-value-input"
               className={INPUT_CLASSNAME}
               type="number"
               value={rule.value}
@@ -370,6 +380,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => {
             />
           ) : hasOperand ? (
             <input
+              data-testid="conditional-formatting-value-input"
               className={INPUT_CLASSNAME}
               value={rule.value}
               onChange={e => onChange({ ...rule, value: e.target.value })}
@@ -381,11 +392,16 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => {
             colors={COLORS}
             onChange={color => onChange({ ...rule, color })}
           />
-          <h3 className="mt3 mb1">{t`Highlight the whole row`}</h3>
-          <Toggle
-            value={rule.highlight_row}
-            onChange={highlight_row => onChange({ ...rule, highlight_row })}
-          />
+          {canHighlightRow && (
+            <>
+              <h3 className="mt3 mb1">{t`Highlight the whole row`}</h3>
+
+              <Toggle
+                value={rule.highlight_row}
+                onChange={highlight_row => onChange({ ...rule, highlight_row })}
+              />
+            </>
+          )}
         </div>
       ) : rule.type === "range" ? (
         <div>
diff --git a/frontend/src/metabase/visualizations/lib/table_format.js b/frontend/src/metabase/visualizations/lib/table_format.js
index 5376543b1b40267a4d2f059ca312391d9d642d3a..e84c01d85224b5d6ab07c96a68199b5df5265f12 100644
--- a/frontend/src/metabase/visualizations/lib/table_format.js
+++ b/frontend/src/metabase/visualizations/lib/table_format.js
@@ -10,16 +10,23 @@ const GRADIENT_ALPHA = 0.75;
 
 // for simplicity wheb typing assume all values are numbers, since you can only pick numeric columns
 
-export function makeCellBackgroundGetter(rows, cols, settings) {
-  const formats = settings["table.column_formatting"] || [];
-  const pivot = settings["table.pivot"];
+export function makeCellBackgroundGetter(
+  rows,
+  cols,
+  formattingSettings,
+  isPivoted,
+) {
   let formatters = {};
   let rowFormatters = [];
   const colIndexes = getColumnIndexesByName(cols);
   try {
-    const columnExtents = computeColumnExtents(formats, rows, colIndexes);
-    formatters = compileFormatters(formats, columnExtents);
-    rowFormatters = compileRowFormatters(formats, columnExtents);
+    const columnExtents = computeColumnExtents(
+      formattingSettings,
+      rows,
+      colIndexes,
+    );
+    formatters = compileFormatters(formattingSettings, columnExtents);
+    rowFormatters = compileRowFormatters(formattingSettings, columnExtents);
   } catch (e) {
     console.error("Unexpected error compiling column formatters: ", e);
   }
@@ -38,7 +45,7 @@ export function makeCellBackgroundGetter(rows, cols, settings) {
         }
       }
       // don't highlight row for pivoted tables
-      if (!pivot) {
+      if (!isPivoted) {
         for (let i = 0; i < rowFormatters.length; i++) {
           const rowFormatter = rowFormatters[i];
           const color = rowFormatter(rows[rowIndex], colIndexes);
diff --git a/frontend/src/metabase/visualizations/visualizations/PivotTable.jsx b/frontend/src/metabase/visualizations/visualizations/PivotTable.jsx
index b5b0e2124d6a103fb1acba8da9cd972e7874211f..a73e028e19ea8ebe83d4f168ae53c3704a01c93e 100644
--- a/frontend/src/metabase/visualizations/visualizations/PivotTable.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/PivotTable.jsx
@@ -6,9 +6,8 @@ import _ from "underscore";
 import { getIn, updateIn } from "icepick";
 import { Grid, Collection, ScrollSync, AutoSizer } from "react-virtualized";
 
-import { darken, lighten } from "metabase/lib/colors";
-import "metabase/visualizations/components/TableInteractive/TableInteractive.css";
 import { getScrollBarSize } from "metabase/lib/dom";
+import ChartSettingsTableFormatting from "metabase/visualizations/components/settings/ChartSettingsTableFormatting";
 
 import Ellipsified from "metabase/core/components/Ellipsified";
 import Icon from "metabase/components/Icon";
@@ -18,6 +17,7 @@ import {
   COLUMN_SPLIT_SETTING,
   COLUMN_SORT_ORDER,
   COLUMN_SHOW_TOTALS,
+  COLUMN_FORMATTING_SETTING,
   isPivotGroupColumn,
   multiLevelPivot,
 } from "metabase/lib/data_grid";
@@ -27,23 +27,13 @@ import { columnSettings } from "metabase/visualizations/lib/settings/column";
 import { findDOMNode } from "react-dom";
 import { connect } from "react-redux";
 import { PLUGIN_SELECTORS } from "metabase/plugins";
-import { RowToggleIconRoot } from "./PivotTable.styled";
-
-const getBgLightColor = (hasCustomColors, isNightMode) => {
-  if (isNightMode) {
-    return lighten("bg-black", 0.3);
-  }
-
-  return hasCustomColors ? darken("white", 0.01) : lighten("brand", 0.65);
-};
-
-const getBgDarkColor = (hasCustomColors, isNightMode) => {
-  if (isNightMode) {
-    return lighten("bg-black", 0.1);
-  }
-
-  return hasCustomColors ? darken("white", 0.035) : lighten("brand", 0.6);
-};
+import {
+  PivotTableRoot,
+  PivotTableCell,
+  PivotTableTopLeftCellsContainer,
+  RowToggleIconRoot,
+  CELL_HEIGHT,
+} from "./PivotTable.styled";
 
 const partitions = [
   {
@@ -71,7 +61,6 @@ const partitions = [
 
 // cell width and height for normal body cells
 const CELL_WIDTH = 100;
-const CELL_HEIGHT = 25;
 // the left header has a wider cell width and some additional spacing on the left to align with the title
 const LEFT_HEADER_LEFT_SPACING = 24;
 const LEFT_HEADER_CELL_WIDTH = 145;
@@ -140,7 +129,7 @@ class PivotTable extends Component {
       },
     },
     [COLUMN_SPLIT_SETTING]: {
-      section: null,
+      section: t`Columns`,
       widget: "fieldsPartition",
       persistDefault: true,
       getHidden: ([{ data }]) =>
@@ -192,6 +181,54 @@ class PivotTable extends Component {
         return addMissingCardBreakouts(setting, card);
       },
     },
+    [COLUMN_FORMATTING_SETTING]: {
+      section: t`Conditional Formatting`,
+      widget: ChartSettingsTableFormatting,
+      default: [],
+      getDefault: ([{ data }], settings) => {
+        const columnFormats = settings[COLUMN_FORMATTING_SETTING] ?? [];
+
+        return columnFormats
+          .map(columnFormat => {
+            const hasOnlyFormattableColumns = columnFormat.columns
+              .map(columnName =>
+                data.cols.find(column => column.name === columnName),
+              )
+              .filter(Boolean)
+              .every(isFormattablePivotColumn);
+
+            if (!hasOnlyFormattableColumns) {
+              return null;
+            }
+
+            return {
+              ...columnFormat,
+              highlight_row: false,
+            };
+          })
+          .filter(Boolean);
+      },
+      isValid: ([{ data }], settings) => {
+        const columnFormats = settings[COLUMN_FORMATTING_SETTING] ?? [];
+
+        return columnFormats.every(columnFormat => {
+          const hasOnlyFormattableColumns = columnFormat.columns
+            .map(columnName =>
+              data.cols.find(column => column.name === columnName),
+            )
+            .filter(Boolean)
+            .every(isFormattablePivotColumn);
+
+          return hasOnlyFormattableColumns && !columnFormat.highlight_row;
+        });
+      },
+      getProps: series => ({
+        canHighlightRow: false,
+        cols: series[0].data.cols.filter(isFormattablePivotColumn),
+      }),
+      getHidden: ([{ data }]) =>
+        !data?.cols.some(col => isFormattablePivotColumn(col)),
+    },
   };
 
   static columnSettings = {
@@ -249,6 +286,10 @@ class PivotTable extends Component {
     this.topHeaderRef && this.topHeaderRef.recomputeCellSizesAndPositions();
   }
 
+  componentDidMount() {
+    this.grid = this.bodyRef && findDOMNode(this.bodyRef);
+  }
+
   render() {
     const {
       settings,
@@ -257,12 +298,13 @@ class PivotTable extends Component {
       hasCustomColors,
       onUpdateVisualizationSettings,
       isNightMode,
+      isDashboard,
     } = this.props;
     if (data == null || !data.cols.some(isPivotGroupColumn)) {
       return null;
     }
 
-    const grid = this.bodyRef && findDOMNode(this.bodyRef);
+    const grid = this.grid;
 
     // In cases where there are horizontal scrollbars are visible AND the data grid has to scroll vertically as well,
     // the left sidebar and the main grid can get out of ScrollSync due to slightly differing heights
@@ -300,49 +342,35 @@ class PivotTable extends Component {
     } = pivoted;
 
     const leftHeaderCellRenderer = ({ index, key, style }) => {
-      const {
-        value,
-        isSubtotal,
-        isGrandTotal,
-        hasChildren,
-        hasSubtotal,
-        depth,
-        path,
-        clicked,
-      } = leftHeaderItems[index];
+      const { value, isSubtotal, hasSubtotal, depth, path, clicked } =
+        leftHeaderItems[index];
+
       return (
-        <div
+        <Cell
           key={key}
           style={{
             ...style,
-            backgroundColor: getBgLightColor(hasCustomColors, isNightMode),
+            ...(depth === 0 ? { paddingLeft: LEFT_HEADER_LEFT_SPACING } : {}),
           }}
-          className={cx("overflow-hidden", {
-            "border-right border-medium": !hasChildren,
-          })}
-        >
-          <Cell
-            style={depth === 0 ? { paddingLeft: LEFT_HEADER_LEFT_SPACING } : {}}
-            value={value}
-            isSubtotal={isSubtotal}
-            isGrandTotal={isGrandTotal}
-            hasCustomColors={hasCustomColors}
-            onClick={this.getCellClickHander(clicked)}
-            isNightMode={isNightMode}
-            icon={
-              (isSubtotal || hasSubtotal) && (
-                <RowToggleIcon
-                  value={path}
-                  settings={settings}
-                  updateSettings={onUpdateVisualizationSettings}
-                  hideUnlessCollapsed={isSubtotal}
-                  rowIndex={rowIndex} // used to get a list of "other" paths when open one item in a collapsed column
-                  isNightMode={isNightMode}
-                />
-              )
-            }
-          />
-        </div>
+          isNightMode={isNightMode}
+          value={value}
+          isEmphasized={isSubtotal}
+          isBold={isSubtotal}
+          onClick={this.getCellClickHander(clicked)}
+          icon={
+            (isSubtotal || hasSubtotal) && (
+              <RowToggleIcon
+                value={path}
+                settings={settings}
+                updateSettings={onUpdateVisualizationSettings}
+                hideUnlessCollapsed={isSubtotal}
+                rowIndex={rowIndex} // used to get a list of "other" paths when open one item in a collapsed column
+                isNightMode={isNightMode}
+              />
+            )
+          }
+        />
+        // </div>
       );
     };
     const leftHeaderCellSizeAndPositionGetter = ({ index }) => {
@@ -364,25 +392,21 @@ class PivotTable extends Component {
     const topHeaderHeight = topHeaderRows * CELL_HEIGHT;
 
     const topHeaderCellRenderer = ({ index, key, style }) => {
-      const { value, hasChildren, clicked } = topHeaderItems[index];
+      const { value, hasChildren, clicked, isSubtotal, maxDepthBelow } =
+        topHeaderItems[index];
       return (
-        <div
+        <Cell
           key={key}
-          style={style}
-          className={cx("px1 flex align-center cursor-pointer", {
-            "border-bottom border-medium": !hasChildren,
-          })}
+          style={{
+            ...style,
+          }}
+          value={value}
+          isNightMode={isNightMode}
+          isBorderedHeader={maxDepthBelow === 0}
+          isEmphasized={hasChildren}
+          isBold={isSubtotal}
           onClick={this.getCellClickHander(clicked)}
-        >
-          <div
-            className={cx("flex flex-full full-height align-center", {
-              "border-bottom": hasChildren,
-            })}
-            style={{ width: "100%" }}
-          >
-            <Ellipsified>{value}</Ellipsified>
-          </div>
-        </div>
+        />
       );
     };
     const topHeaderCellSizeAndPositionGetter = ({ index }) => {
@@ -405,16 +429,16 @@ class PivotTable extends Component {
     const bodyRenderer = ({ key, style, rowIndex, columnIndex }) => (
       <div key={key} style={style} className="flex">
         {getRowSection(columnIndex, rowIndex).map(
-          ({ value, isSubtotal, isGrandTotal, clicked }, index) => (
+          ({ value, isSubtotal, clicked, backgroundColor }, index) => (
             <Cell
+              isNightMode={isNightMode}
               key={index}
               value={value}
-              isSubtotal={isSubtotal}
-              isGrandTotal={isGrandTotal}
-              hasCustomColors={hasCustomColors}
-              isNightMode={isNightMode}
+              isEmphasized={isSubtotal}
+              isBold={isSubtotal}
               isBody
               onClick={this.getCellClickHander(clicked)}
+              backgroundColor={backgroundColor}
             />
           ),
         )}
@@ -422,34 +446,37 @@ class PivotTable extends Component {
     );
 
     return (
-      <div className="no-outline text-small full-height">
+      <PivotTableRoot isDashboard={isDashboard} isNightMode={isNightMode}>
         <ScrollSync>
           {({ onScroll, scrollLeft, scrollTop }) => (
             <div className="full-height flex flex-column">
               <div className="flex" style={{ height: topHeaderHeight }}>
                 {/* top left corner - displays left header columns */}
-                <div
-                  className={cx("flex align-end", {
-                    "border-right border-bottom border-medium": leftHeaderWidth,
-                  })}
+                <PivotTableTopLeftCellsContainer
+                  isNightMode={isNightMode}
                   style={{
-                    backgroundColor: getBgLightColor(
-                      hasCustomColors,
-                      isNightMode,
-                    ),
-                    // add left spacing unless the header width is 0
-                    paddingLeft: leftHeaderWidth && LEFT_HEADER_LEFT_SPACING,
                     width: leftHeaderWidth,
-                    height: topHeaderHeight,
                   }}
                 >
                   {rowIndexes.map((rowIndex, index) => (
                     <Cell
                       key={rowIndex}
-                      value={this.getColumnTitle(rowIndex)}
-                      style={{ width: LEFT_HEADER_CELL_WIDTH }}
-                      hasCustomColors={hasCustomColors}
+                      isEmphasized
+                      isBold
+                      isBorderedHeader
+                      isTransparent
+                      hasTopBorder={topHeaderRows > 1}
                       isNightMode={isNightMode}
+                      value={this.getColumnTitle(rowIndex)}
+                      style={{
+                        width: LEFT_HEADER_CELL_WIDTH,
+                        ...(index === 0
+                          ? { paddingLeft: LEFT_HEADER_LEFT_SPACING }
+                          : {}),
+                        ...(index === rowIndexes.length - 1
+                          ? { borderRight: "none" }
+                          : {}),
+                      }}
                       icon={
                         // you can only collapse before the last column
                         index < rowIndexes.length - 1 &&
@@ -465,11 +492,12 @@ class PivotTable extends Component {
                       }
                     />
                   ))}
-                </div>
+                </PivotTableTopLeftCellsContainer>
                 {/* top header */}
                 <Collection
                   ref={e => (this.topHeaderRef = e)}
-                  className="scroll-hide-all text-medium"
+                  className="scroll-hide-all"
+                  isNightMode={isNightMode}
                   width={width - leftHeaderWidth}
                   height={topHeaderHeight}
                   cellCount={topHeaderItems.length}
@@ -527,7 +555,7 @@ class PivotTable extends Component {
             </div>
           )}
         </ScrollSync>
-      </div>
+      </PivotTableRoot>
     );
   }
 
@@ -605,13 +633,6 @@ function RowToggleIcon({
 
   return (
     <RowToggleIconRoot
-      style={{
-        padding: "4px",
-        borderRadius: "4px",
-        backgroundColor: isCollapsed
-          ? getBgLightColor(hasCustomColors, isNightMode)
-          : getBgDarkColor(hasCustomColors, isNightMode),
-      }}
       onClick={e => {
         e.stopPropagation();
         updateSettings({
@@ -626,43 +647,42 @@ function RowToggleIcon({
 
 function Cell({
   value,
-  isSubtotal,
-  isGrandTotal,
-  onClick,
   style,
-  isBody = false,
-  className,
   icon,
-  hasCustomColors,
+  backgroundColor,
+  isBody = false,
+  isBold,
+  isEmphasized,
   isNightMode,
+  isBorderedHeader,
+  isTransparent,
+  hasTopBorder,
+  onClick,
 }) {
   return (
-    <div
+    <PivotTableCell
+      data-testid="pivot-table-cell"
+      isNightMode={isNightMode}
+      isBold={isBold}
+      isEmphasized={isEmphasized}
+      isBorderedHeader={isBorderedHeader}
+      hasTopBorder={hasTopBorder}
+      isTransparent={isTransparent}
       style={{
-        lineHeight: `${CELL_HEIGHT}px`,
-        ...(isGrandTotal ? { borderTop: "1px solid white" } : {}),
         ...style,
-        ...(isSubtotal
+        ...(backgroundColor
           ? {
-              backgroundColor: getBgDarkColor(hasCustomColors, isNightMode),
+              backgroundColor,
             }
           : {}),
       }}
-      className={cx(
-        "shrink-below-content-size flex-full flex-basis-none TableInteractive-cellWrapper",
-        className,
-        {
-          "text-bold": isSubtotal,
-          "cursor-pointer": onClick,
-        },
-      )}
       onClick={onClick}
     >
       <div className={cx("px1 flex align-center", { "justify-end": isBody })}>
         <Ellipsified>{value}</Ellipsified>
         {icon && <div className="pl1">{icon}</div>}
       </div>
-    </div>
+    </PivotTableCell>
   );
 }
 
@@ -718,3 +738,7 @@ function isColumnValid(col) {
     isPivotGroupColumn(col)
   );
 }
+
+function isFormattablePivotColumn(column) {
+  return column.source === "aggregation";
+}
diff --git a/frontend/src/metabase/visualizations/visualizations/PivotTable.styled.tsx b/frontend/src/metabase/visualizations/visualizations/PivotTable.styled.tsx
index 48696f60a86b52cb2ff2fa73779a43f2b87b2be5..037a2f1ea5a383e8af7a3b8b6de81a4cc2d67160 100644
--- a/frontend/src/metabase/visualizations/visualizations/PivotTable.styled.tsx
+++ b/frontend/src/metabase/visualizations/visualizations/PivotTable.styled.tsx
@@ -1,13 +1,113 @@
+import { css } from "@emotion/react";
 import styled from "@emotion/styled";
-import { color } from "metabase/lib/colors";
+import { color, alpha, darken } from "metabase/lib/colors";
+
+export const CELL_HEIGHT = 30;
 
 export const RowToggleIconRoot = styled.div`
   display: flex;
   align-items: center;
   cursor: pointer;
-  color: ${color("text-light")};
+  color: ${color("white")};
+  padding: 4px;
+  border-radius: 4px;
+  background-color: ${color("text-light")};
+  transition: all 200ms;
+  outline: none;
+
+  &:hover {
+    background-color: ${darken("text-light", 0.2)};
+  }
+`;
+
+interface PivotTableCellProps {
+  isBold?: boolean;
+  isEmphasized?: boolean;
+  isNightMode?: boolean;
+  isBorderedHeader?: boolean;
+  hasTopBorder?: boolean;
+  isTransparent?: boolean;
+}
+
+const getCellBackgroundColor = ({
+  isEmphasized,
+  isNightMode,
+  isTransparent,
+}: Partial<PivotTableCellProps>) => {
+  if (isTransparent) {
+    return "transparent";
+  }
+
+  if (!isEmphasized) {
+    return isNightMode ? alpha("bg-black", 0.1) : color("white");
+  }
+
+  return isNightMode ? color("bg-black") : alpha("border", 0.25);
+};
+
+const getColor = ({ isNightMode }: PivotTableCellProps) => {
+  return isNightMode ? color("white") : color("text-dark");
+};
+
+const getBorderColor = ({ isNightMode }: PivotTableCellProps) => {
+  return isNightMode ? alpha("bg-black", 0.8) : color("border");
+};
+
+export const PivotTableCell = styled.div<PivotTableCellProps>`
+  flex: 1 0 auto;
+  flex-basis: 0;
+  line-height: ${CELL_HEIGHT}px;
+  min-width: 0;
+  min-height: 0;
+  font-weight: ${props => (props.isBold ? "bold" : "normal")};
+  cursor: ${props => (props.onClick ? "pointer" : "default")};
+  color: ${getColor};
+  box-shadow: -1px 0 0 0 ${getBorderColor} inset;
+  border-bottom: 1px solid
+    ${props =>
+      props.isBorderedHeader ? color("bg-dark") : getBorderColor(props)};
+  background-color: ${getCellBackgroundColor};
+  ${props =>
+    props.hasTopBorder &&
+    css`
+      // compensate the top border
+      line-height: ${CELL_HEIGHT - 1}px;
+      border-top: 1px solid ${getBorderColor(props)};
+    `}
 
   &:hover {
-    color: ${color("brand")};
+    background-color: ${color("border")};
   }
 `;
+
+interface PivotTableTopLeftCellsContainerProps {
+  isNightMode?: boolean;
+}
+
+export const PivotTableTopLeftCellsContainer = styled.div<PivotTableTopLeftCellsContainerProps>`
+  display: flex;
+  align-items: flex-end;
+  box-shadow: -1px 0 0 0 ${getBorderColor} inset;
+  background-color: ${props =>
+    getCellBackgroundColor({
+      isEmphasized: true,
+      isNightMode: props.isNightMode,
+    })};
+`;
+
+interface PivotTableRootProps {
+  isDashboard?: boolean;
+  isNightMode?: boolean;
+}
+
+export const PivotTableRoot = styled.div<PivotTableRootProps>`
+  height: 100%;
+  font-size: 0.875em;
+
+  ${props =>
+    props.isDashboard
+      ? css`
+          border-top: 1px solid ${getBorderColor(props)};
+        `
+      : null}
+`;
diff --git a/frontend/src/metabase/visualizations/visualizations/Table.jsx b/frontend/src/metabase/visualizations/visualizations/Table.jsx
index 37fb27f9f9f93836b92d996efb60d7e7a4ad7066..56e5e9167f7e170bc85b77abaaf69a7db3fe48a6 100644
--- a/frontend/src/metabase/visualizations/visualizations/Table.jsx
+++ b/frontend/src/metabase/visualizations/visualizations/Table.jsx
@@ -197,7 +197,7 @@ export default class Table extends Component {
       }),
     },
     "table.column_widths": {},
-    "table.column_formatting": {
+    [DataGrid.COLUMN_FORMATTING_SETTING]: {
       section: t`Conditional Formatting`,
       widget: ChartSettingsTableFormatting,
       default: [],
@@ -225,9 +225,14 @@ export default class Table extends Component {
         ],
         settings,
       ) {
-        return makeCellBackgroundGetter(rows, cols, settings);
+        return makeCellBackgroundGetter(
+          rows,
+          cols,
+          settings[DataGrid.COLUMN_FORMATTING_SETTING] ?? [],
+          settings["table.pivot"],
+        );
       },
-      readDependencies: ["table.column_formatting", "table.pivot"],
+      readDependencies: [DataGrid.COLUMN_FORMATTING_SETTING, "table.pivot"],
     },
   };
 
diff --git a/frontend/test/metabase/scenarios/visualizations/pivot_tables.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/pivot_tables.cy.spec.js
index e07ead3aa48b7369fca6f1e0c49be957cf5e308e..6b54b04acc0145c868cf16cab6034a020a48cb59 100644
--- a/frontend/test/metabase/scenarios/visualizations/pivot_tables.cy.spec.js
+++ b/frontend/test/metabase/scenarios/visualizations/pivot_tables.cy.spec.js
@@ -294,12 +294,12 @@ describe("scenarios > visualizations > pivot tables", () => {
 
     cy.log("Collapse the options panel");
     cy.icon("chevronup").click();
-    cy.findByText(/Formatting/).should("not.exist");
+    cy.findByText("Formatting").should("not.exist");
     cy.findByText(/See options/).should("not.exist");
 
     cy.log("Expand it again");
     cy.icon("chevrondown").first().click();
-    cy.findByText(/Formatting/);
+    cy.findByText("Formatting");
     cy.findByText(/See options/);
   });
 
@@ -340,7 +340,7 @@ describe("scenarios > visualizations > pivot tables", () => {
       .parent()
       .findAllByText(/Count/)
       .click();
-    cy.findByText(/Formatting/);
+    cy.findByText("Formatting");
     cy.findByText(/See options/).click();
 
     cy.log("New panel for the column options");
@@ -370,7 +370,7 @@ describe("scenarios > visualizations > pivot tables", () => {
       .findAllByText(/Count/)
       .click();
 
-    cy.findByText(/Formatting/);
+    cy.findByText("Formatting");
     cy.findByText(/Sort order/).should("not.exist");
   });
 
@@ -831,6 +831,59 @@ describe("scenarios > visualizations > pivot tables", () => {
     cy.findAllByText(/Totals for .*/i).should("have.length", 0);
   });
 
+  it("should apply conditional formatting", () => {
+    visitQuestionAdhoc({
+      dataset_query: {
+        type: "query",
+        query: {
+          "source-table": ORDERS_ID,
+          aggregation: [["sum", ["field", ORDERS.SUBTOTAL, null]]],
+          breakout: [
+            ["field", ORDERS.CREATED_AT, { "temporal-unit": "year" }],
+            ["field", PRODUCTS.CATEGORY, { "source-field": ORDERS.PRODUCT_ID }],
+            ["field", PEOPLE.STATE, { "source-field": ORDERS.USER_ID }],
+          ],
+          filter: [">", ["field", ORDERS.CREATED_AT, null], "2020-01-01"],
+        },
+        database: SAMPLE_DB_ID,
+      },
+      display: "pivot",
+      visualization_settings: {
+        "pivot_table.column_split": {
+          rows: [
+            ["field", PEOPLE.STATE, { "source-field": ORDERS.USER_ID }],
+            ["field", ORDERS.CREATED_AT, { "temporal-unit": "year" }],
+          ],
+          columns: [
+            ["field", PRODUCTS.CATEGORY, { "source-field": ORDERS.PRODUCT_ID }],
+          ],
+          values: [["aggregation", 0]],
+        },
+        "pivot_table.collapsed_rows": {
+          value: [],
+          rows: [
+            ["field", PEOPLE.STATE, { "source-field": ORDERS.USER_ID }],
+            ["field", ORDERS.CREATED_AT, { "temporal-unit": "year" }],
+          ],
+        },
+      },
+    });
+
+    cy.findByText("Settings").click();
+    cy.findByText("Conditional Formatting").click();
+
+    cy.findByText("Add a rule").click();
+    cy.findByTestId("conditional-formatting-value-input").type("70");
+    cy.findByText("is equal to").click();
+    cy.findByText("is less than or equal to").click();
+
+    cy.contains("[data-testid=pivot-table-cell]", "65.09").should(
+      "have.css",
+      "background-color",
+      "rgba(80, 158, 227, 0.65)",
+    );
+  });
+
   it.skip("should sort by metric (metabase#22872)", () => {
     const questionDetails = {
       dataset_query: {