diff --git a/draftlogs/7420_add.md b/draftlogs/7420_add.md
new file mode 100644
index 00000000000..5dd6fc712c6
--- /dev/null
+++ b/draftlogs/7420_add.md
@@ -0,0 +1 @@
+ - Add support for ticklabelposition "inside"/"outside" for category axes with `tickson` set to "boundaries" [[#7420](https://github.com/plotly/plotly.js/pull/7420)]
diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js
index c293d99b564..254ded465e9 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -3073,6 +3073,7 @@ function getPosX(d) {
 // v is a shift perpendicular to the axis
 function getTickLabelUV(ax) {
     var ticklabelposition = ax.ticklabelposition || '';
+    var tickson = ax.tickson || '';
     var has = function(str) {
         return ticklabelposition.indexOf(str) !== -1;
     };
@@ -3083,7 +3084,7 @@ function getTickLabelUV(ax) {
     var isBottom = has('bottom');
     var isInside = has('inside');
 
-    var isAligned = isBottom || isLeft || isTop || isRight;
+    var isAligned = (tickson != 'boundaries') && (isBottom || isLeft || isTop || isRight);
 
     // early return
     if(!isAligned && !isInside) return [0, 0];
@@ -3167,6 +3168,8 @@ axes.makeTickPath = function(ax, shift, sgn, opts) {
  */
 axes.makeLabelFns = function(ax, shift, angle) {
     var ticklabelposition = ax.ticklabelposition || '';
+    var tickson = ax.tickson || '';
+
     var has = function(str) {
         return ticklabelposition.indexOf(str) !== -1;
     };
@@ -3175,12 +3178,12 @@ axes.makeLabelFns = function(ax, shift, angle) {
     var isLeft = has('left');
     var isRight = has('right');
     var isBottom = has('bottom');
-    var isAligned = isBottom || isLeft || isTop || isRight;
+    var isAligned = (tickson != 'boundaries') && (isBottom || isLeft || isTop || isRight);
 
     var insideTickLabels = has('inside');
     var labelsOverTicks =
         (ticklabelposition === 'inside' && ax.ticks === 'inside') ||
-        (!insideTickLabels && ax.ticks === 'outside' && ax.tickson !== 'boundaries');
+        (!insideTickLabels && ax.ticks === 'outside' && tickson !== 'boundaries');
 
     var labelStandoff = 0;
     var labelShift = 0;
@@ -3895,6 +3898,8 @@ axes.drawLabels = function(gd, ax, opts) {
                 }
             } else {
                 var ticklabelposition = ax.ticklabelposition || '';
+                var tickson = ax.tickson ||'';
+
                 var has = function(str) {
                     return ticklabelposition.indexOf(str) !== -1;
                 };
@@ -3902,7 +3907,7 @@ axes.drawLabels = function(gd, ax, opts) {
                 var isLeft = has('left');
                 var isRight = has('right');
                 var isBottom = has('bottom');
-                var isAligned = isBottom || isLeft || isTop || isRight;
+                var isAligned = (tickson != 'boundaries') && (isBottom || isLeft || isTop || isRight);
                 var pad = !isAligned ? 0 :
                 (ax.tickwidth || 0) + 2 * TEXTPAD;
 
diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js
index 8c5c75d8832..4531785cd2f 100644
--- a/src/plots/cartesian/axis_defaults.js
+++ b/src/plots/cartesian/axis_defaults.js
@@ -167,11 +167,11 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
         (axType === 'category' || isMultiCategory) &&
         (containerOut.ticks || containerOut.showgrid)
     ) {
-        var ticksonDflt;
-        if(isMultiCategory) ticksonDflt = 'boundaries';
-        var tickson = coerce('tickson', ticksonDflt);
-        if(tickson === 'boundaries') {
+        if (isMultiCategory) {
+            coerce('tickson', 'boundaries');
             delete containerOut.ticklabelposition;
+        } else { // category axis
+            coerce('tickson');
         }
     }
 
diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js
index 978f545e53e..f1b7bbbe5d6 100644
--- a/src/plots/cartesian/layout_attributes.js
+++ b/src/plots/cartesian/layout_attributes.js
@@ -663,12 +663,13 @@ module.exports = {
         dflt: 'outside',
         editType: 'calc',
         description: [
-            'Determines where tick labels are drawn with respect to the axis',
+            'Determines where tick labels are drawn with respect to the axis.',
             'Please note that',
-            'top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*.',
-            'Similarly',
-            'left or right has no effect on y axes or when `ticklabelmode` is set to *period*.',
-            'Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*.',
+            'top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*',
+            'or when `tickson` is set to *boundaries*. Similarly,',
+            'left or right has no effect on y axes or when `ticklabelmode` is set to *period*',
+            'or when `tickson` is set to *boundaries*.',
+            'Has no effect on *multicategory* axes.',
             'When used on axes linked by `matches` or `scaleanchor`,',
             'no extra padding for inside labels would be added by autorange,',
             'so that the scales could match.'
diff --git a/test/image/baselines/zz-tickson_boundaries_ticklabelposition.png b/test/image/baselines/zz-tickson_boundaries_ticklabelposition.png
new file mode 100644
index 00000000000..5acf8e33d08
Binary files /dev/null and b/test/image/baselines/zz-tickson_boundaries_ticklabelposition.png differ
diff --git a/test/image/mocks/zz-tickson_boundaries_ticklabelposition.json b/test/image/mocks/zz-tickson_boundaries_ticklabelposition.json
new file mode 100644
index 00000000000..01962a67706
--- /dev/null
+++ b/test/image/mocks/zz-tickson_boundaries_ticklabelposition.json
@@ -0,0 +1,150 @@
+{
+ "data": [
+  {
+   "type": "box",
+   "x": [
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2"
+   ],
+   "y": [0.2, 0.2, 0.6, 1, 0.5, 0.4, 0.2, 0.7, 0.9, 0.1, 0.5, 0.3]
+  },
+  {
+   "type": "box",
+   "x": [
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2"
+   ],
+   "y": [0.1, 0.3, 0.1, 0.9, 0.6, 0.6, 0.9, 1, 0.3, 0.6, 0.8, 0.5]
+  },
+  {
+   "type": "box",
+   "x": [
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 1",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2",
+    "day 2"
+   ],
+   "y": [0.6, 0.7, 0.3, 0.6, 0, 0.5, 0.7, 0.9, 0.5, 0.8, 0.7, 0.2]
+  },
+
+  {
+   "type": "bar",
+   "x": [1, 2, 1],
+   "y": ["apples", "bananas", "clementines"],
+   "orientation": "h",
+   "xaxis": "x2",
+   "yaxis": "y2"
+  },
+  {
+   "type": "bar",
+   "x": [1.3, 2.2, 0.8],
+   "y": ["apples", "bananas", "clementines"],
+   "orientation": "h",
+   "xaxis": "x2",
+   "yaxis": "y2"
+  },
+  {
+   "type": "bar",
+   "x": [3, 3.2, 1.8],
+   "y": ["apples", "bananas", "clementines"],
+   "orientation": "h",
+   "xaxis": "x2",
+   "yaxis": "y2"
+  },
+
+  {
+   "type": "bar",
+   "name": "with dtick !== 1",
+   "x": ["a", "b", "c", "d", "e", "f", "g", "h"],
+   "y": [1, 2, 1, 2, 1, 3, 4, 1],
+   "xaxis": "x3",
+   "yaxis": "y3"
+  },
+
+  {
+   "mode": "markers",
+   "marker": { "symbol": "square" },
+   "name": "with overlapping tick labels",
+   "x": ["A very long title", "short", "Another very long title"],
+   "y": [0, 10, 2],
+   "xaxis": "x4",
+   "yaxis": "y4"
+  }
+ ],
+ "layout": {
+  "title": {
+    "text": "Although some ticklabelpositions have a side specified,<br>all category labels are expected to be centered."
+  },
+  "boxmode": "group",
+  "grid": {
+   "rows": 4,
+   "columns": 1,
+   "pattern": "independent",
+   "ygap": 0.2
+  },
+  "xaxis": {
+   "ticklabelposition": "inside right",
+   "ticks": "outside",
+   "tickson": "boundaries",
+   "gridcolor": "white",
+   "gridwidth": 4
+  },
+  "yaxis2": {
+   "ticks": "inside",
+   "ticklabelposition": "inside top",
+   "tickson": "boundaries",
+   "gridcolor": "white",
+   "gridwidth": 4
+  },
+  "xaxis3": {
+   "ticks": "inside",
+   "ticklabelposition": "inside left",
+   "tickson": "boundaries",
+   "gridcolor": "white",
+   "gridwidth": 4,
+   "dtick": 2
+  },
+  "xaxis4": {
+   "domain": [0.22, 0.78],
+   "ticks": "outside",
+   "ticklabelposition": "inside",
+   "ticklen": 20,
+   "tickson": "boundaries",
+   "gridcolor": "white",
+   "gridwidth": 4
+  },
+  "plot_bgcolor": "lightgrey",
+  "showlegend": false,
+  "width": 500,
+  "height": 800,
+  "margin": { "b": 140 }
+ }
+}
diff --git a/test/plot-schema.json b/test/plot-schema.json
index 403428a1457..25256d4b2a4 100644
--- a/test/plot-schema.json
+++ b/test/plot-schema.json
@@ -14711,7 +14711,7 @@
      ]
     },
     "ticklabelposition": {
-     "description": "Determines where tick labels are drawn with respect to the axis Please note that top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*. Similarly left or right has no effect on y axes or when `ticklabelmode` is set to *period*. Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*. When used on axes linked by `matches` or `scaleanchor`, no extra padding for inside labels would be added by autorange, so that the scales could match.",
+     "description": "Determines where tick labels are drawn with respect to the axis. Please note that top or bottom has no effect on x axes or when `ticklabelmode` is set to *period* or when `tickson` is set to *boundaries*. Similarly, left or right has no effect on y axes or when `ticklabelmode` is set to *period* or when `tickson` is set to *boundaries*. Has no effect on *multicategory* axes. When used on axes linked by `matches` or `scaleanchor`, no extra padding for inside labels would be added by autorange, so that the scales could match.",
      "dflt": "outside",
      "editType": "calc",
      "valType": "enumerated",
@@ -15955,7 +15955,7 @@
      ]
     },
     "ticklabelposition": {
-     "description": "Determines where tick labels are drawn with respect to the axis Please note that top or bottom has no effect on x axes or when `ticklabelmode` is set to *period*. Similarly left or right has no effect on y axes or when `ticklabelmode` is set to *period*. Has no effect on *multicategory* axes or when `tickson` is set to *boundaries*. When used on axes linked by `matches` or `scaleanchor`, no extra padding for inside labels would be added by autorange, so that the scales could match.",
+     "description": "Determines where tick labels are drawn with respect to the axis. Please note that top or bottom has no effect on x axes or when `ticklabelmode` is set to *period* or when `tickson` is set to *boundaries*. Similarly, left or right has no effect on y axes or when `ticklabelmode` is set to *period* or when `tickson` is set to *boundaries*. Has no effect on *multicategory* axes. When used on axes linked by `matches` or `scaleanchor`, no extra padding for inside labels would be added by autorange, so that the scales could match.",
      "dflt": "outside",
      "editType": "calc",
      "valType": "enumerated",