diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index 4df1953d3ff..55a4123cb70 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -755,10 +755,12 @@ function alignHTMLWith(_base, container, options) {
     };
 }
 
-exports.makeTextShadow = function(offset, color) {
-    var x = offset + 'px ';
-    var y = offset + 'px ';
-    var b = '1px ';
+var onePx = '1px ';
+
+exports.makeTextShadow = function(color) {
+    var x = onePx;
+    var y = onePx;
+    var b = onePx;
     return x + y + b + color + ', ' +
         '-' + x + '-' + y + b + color + ', ' +
         x + '-' + y + b + color + ', ' +
diff --git a/src/traces/parcats/parcats.js b/src/traces/parcats/parcats.js
index 77bd227c29f..15f84b8bcb3 100644
--- a/src/traces/parcats/parcats.js
+++ b/src/traces/parcats/parcats.js
@@ -238,7 +238,7 @@ function performPlot(parcatsModels, graphDiv, layout, svg) {
             })
         .attr('alignment-baseline', 'middle')
 
-        .style('text-shadow', svgTextUtils.makeTextShadow(1, paperColor))
+        .style('text-shadow', svgTextUtils.makeTextShadow(paperColor))
         .style('fill', 'rgb(0, 0, 0)')
         .attr('x',
             function(d) {
diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js
index 28cc48c89c8..af65f2e0131 100644
--- a/src/traces/parcoords/parcoords.js
+++ b/src/traces/parcoords/parcoords.js
@@ -649,7 +649,7 @@ module.exports = function parcoords(gd, cdModule, layout, callbacks) {
         .attr('stroke-width', '1px');
 
     axis.selectAll('text')
-        .style('text-shadow', svgTextUtils.makeTextShadow(1, paperColor))
+        .style('text-shadow', svgTextUtils.makeTextShadow(paperColor))
         .style('cursor', 'default');
 
     var axisHeading = axisOverlays.selectAll('.' + c.cn.axisHeading)
diff --git a/src/traces/sankey/constants.js b/src/traces/sankey/constants.js
index 3145c55261d..c7eda8ca65b 100644
--- a/src/traces/sankey/constants.js
+++ b/src/traces/sankey/constants.js
@@ -16,10 +16,6 @@ module.exports = {
         sankeyNodeSet: 'sankey-node-set',
         sankeyNode: 'sankey-node',
         nodeRect: 'node-rect',
-        nodeCapture: 'node-capture',
-        nodeCentered: 'node-entered',
-        nodeLabelGuide: 'node-label-guide',
-        nodeLabel: 'node-label',
-        nodeLabelTextPath: 'node-label-text-path'
+        nodeLabel: 'node-label'
     }
 };
diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js
index fb289ae1d60..ced03a514f5 100644
--- a/src/traces/sankey/render.js
+++ b/src/traces/sankey/render.js
@@ -1,24 +1,31 @@
 'use strict';
 
-var c = require('./constants');
+var d3Force = require('d3-force');
+var interpolateNumber = require('d3-interpolate').interpolateNumber;
 var d3 = require('@plotly/d3');
+var d3Sankey = require('@plotly/d3-sankey');
+var d3SankeyCircular = require('@plotly/d3-sankey-circular');
+
+var c = require('./constants');
 var tinycolor = require('tinycolor2');
 var Color = require('../../components/color');
 var Drawing = require('../../components/drawing');
-var d3Sankey = require('@plotly/d3-sankey');
-var d3SankeyCircular = require('@plotly/d3-sankey-circular');
-var d3Force = require('d3-force');
 var Lib = require('../../lib');
 var strTranslate = Lib.strTranslate;
+var strRotate = Lib.strRotate;
 var gup = require('../../lib/gup');
 var keyFun = gup.keyFun;
 var repeat = gup.repeat;
 var unwrap = gup.unwrap;
-var interpolateNumber = require('d3-interpolate').interpolateNumber;
 var svgTextUtils = require('../../lib/svg_text_utils');
 
 var Registry = require('../../registry');
 
+var alignmentConstants = require('../../constants/alignment');
+var CAP_SHIFT = alignmentConstants.CAP_SHIFT;
+var LINE_SPACING = alignmentConstants.LINE_SPACING;
+var TEXTPAD = 3;
+
 // view models
 
 function sankeyModel(layout, d, traceIndex) {
@@ -547,22 +554,6 @@ function sankeyTransform(d) {
     return offset + (d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)');
 }
 
-function nodeCentering(d) {
-    return strTranslate(d.horizontal ? 0 : d.labelY, d.horizontal ? d.labelY : 0);
-}
-
-function textGuidePath(d) {
-    return d3.svg.line()([
-        [d.horizontal ? (d.left ? -d.sizeAcross : d.visibleWidth + c.nodeTextOffsetHorizontal) : c.nodeTextOffsetHorizontal, 0],
-        [d.horizontal ? (d.left ? - c.nodeTextOffsetHorizontal : d.sizeAcross) : d.visibleHeight - c.nodeTextOffsetHorizontal, 0]
-    ]);
-}
-
-function sankeyInverseTransform(d) {return d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)';}
-function textFlip(d) {return d.horizontal ? 'scale(1 1)' : 'scale(-1 1)';}
-function nodeTextColor(d) {return d.darkBackground && !d.horizontal ? 'rgb(255,255,255)' : 'rgb(0,0,0)';}
-function nodeTextOffset(d) {return d.horizontal && d.left ? '100%' : '0%';}
-
 // event handling
 
 function attachPointerEvents(selection, sankey, eventSet) {
@@ -970,88 +961,55 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
         .ease(c.ease).duration(c.duration)
         .call(sizeNode);
 
-    var nodeCapture = sankeyNode.selectAll('.' + c.cn.nodeCapture)
-        .data(repeat);
-
-    nodeCapture.enter()
-        .append('rect')
-        .classed(c.cn.nodeCapture, true)
-        .style('fill-opacity', 0);
-
-    nodeCapture
-        .attr('x', function(d) {return d.zoneX;})
-        .attr('y', function(d) {return d.zoneY;})
-        .attr('width', function(d) {return d.zoneWidth;})
-        .attr('height', function(d) {return d.zoneHeight;});
-
-    var nodeCentered = sankeyNode.selectAll('.' + c.cn.nodeCentered)
-        .data(repeat);
-
-    nodeCentered.enter()
-        .append('g')
-        .classed(c.cn.nodeCentered, true)
-        .attr('transform', nodeCentering);
-
-    nodeCentered
-        .transition()
-        .ease(c.ease).duration(c.duration)
-        .attr('transform', nodeCentering);
-
-    var nodeLabelGuide = nodeCentered.selectAll('.' + c.cn.nodeLabelGuide)
-        .data(repeat);
-
-    nodeLabelGuide.enter()
-        .append('path')
-        .classed(c.cn.nodeLabelGuide, true)
-        .attr('id', function(d) {return d.uniqueNodeLabelPathId;})
-        .attr('d', textGuidePath)
-        .attr('transform', sankeyInverseTransform);
-
-    nodeLabelGuide
-        .transition()
-        .ease(c.ease).duration(c.duration)
-        .attr('d', textGuidePath)
-        .attr('transform', sankeyInverseTransform);
-
-    var nodeLabel = nodeCentered.selectAll('.' + c.cn.nodeLabel)
+    var nodeLabel = sankeyNode.selectAll('.' + c.cn.nodeLabel)
         .data(repeat);
 
     nodeLabel.enter()
         .append('text')
         .classed(c.cn.nodeLabel, true)
-        .attr('transform', textFlip)
-        .style('cursor', 'default')
-        .style('fill', 'black');
+        .style('cursor', 'default');
 
     nodeLabel
-        .style('text-shadow', function(d) {
-            return d.horizontal ? svgTextUtils.makeTextShadow(1, '#fff') : 'none';
+        .attr('data-notex', 1) // prohibit tex interpretation until we can handle tex and regular text together
+        .text(function(d) { return d.node.label; })
+        .each(function(d) {
+            var e = d3.select(this);
+            Drawing.font(e, d.textFont);
+            svgTextUtils.convertToTspans(e, gd);
         })
-        .each(function(d) {Drawing.font(nodeLabel, d.textFont);});
-
-    nodeLabel
-        .transition()
-        .ease(c.ease).duration(c.duration)
-        .attr('transform', textFlip);
-
-    var nodeLabelTextPath = nodeLabel.selectAll('.' + c.cn.nodeLabelTextPath)
-        .data(repeat);
+        .style('text-shadow', svgTextUtils.makeTextShadow(gd._fullLayout.paper_bgcolor))
+        .attr('text-anchor', function(d) {
+            return (d.horizontal && d.left) ? 'end' : 'start';
+        })
+        .attr('transform', function(d) {
+            var e = d3.select(this);
+            // how much to shift a multi-line label to center it vertically.
+            var nLines = svgTextUtils.lineCount(e);
+            var blockHeight = d.textFont.size * (
+                (nLines - 1) * LINE_SPACING - CAP_SHIFT
+            );
+
+            var posX = d.nodeLineWidth / 2 + TEXTPAD;
+            var posY = ((d.horizontal ? d.visibleHeight : d.visibleWidth) - blockHeight) / 2;
+            if(d.horizontal) {
+                if(d.left) {
+                    posX = -posX;
+                } else {
+                    posX += d.visibleWidth;
+                }
+            }
 
-    nodeLabelTextPath.enter()
-        .append('textPath')
-        .classed(c.cn.nodeLabelTextPath, true)
-        .attr('alignment-baseline', 'middle')
-        .attr('xlink:href', function(d) {return '#' + d.uniqueNodeLabelPathId;})
-        .attr('startOffset', nodeTextOffset)
-        .style('fill', nodeTextColor);
+            var flipText = d.horizontal ? '' : (
+                'scale(-1,1)' + strRotate(90)
+            );
 
-    nodeLabelTextPath
-        .text(function(d) {return d.horizontal || d.node.dy > 5 ? d.node.label : '';})
-        .attr('text-anchor', function(d) {return d.horizontal && d.left ? 'end' : 'start';});
+            return strTranslate(
+                d.horizontal ? posX : posY,
+                d.horizontal ? posY : posX
+            ) + flipText;
+        });
 
-    nodeLabelTextPath
+    nodeLabel
         .transition()
-        .ease(c.ease).duration(c.duration)
-        .attr('startOffset', nodeTextOffset)
-        .style('fill', nodeTextColor);
+        .ease(c.ease).duration(c.duration);
 };
diff --git a/test/image/baselines/sankey_circular.png b/test/image/baselines/sankey_circular.png
index f2ba831b652..a85f6c8fe98 100644
Binary files a/test/image/baselines/sankey_circular.png and b/test/image/baselines/sankey_circular.png differ
diff --git a/test/image/baselines/sankey_circular_large.png b/test/image/baselines/sankey_circular_large.png
index 600ff74b614..97844d0ae23 100644
Binary files a/test/image/baselines/sankey_circular_large.png and b/test/image/baselines/sankey_circular_large.png differ
diff --git a/test/image/baselines/sankey_circular_process.png b/test/image/baselines/sankey_circular_process.png
index 3be8d36284a..ab285e48f77 100644
Binary files a/test/image/baselines/sankey_circular_process.png and b/test/image/baselines/sankey_circular_process.png differ
diff --git a/test/image/baselines/sankey_circular_simple.png b/test/image/baselines/sankey_circular_simple.png
index 8c6a99f2e26..295945716d5 100644
Binary files a/test/image/baselines/sankey_circular_simple.png and b/test/image/baselines/sankey_circular_simple.png differ
diff --git a/test/image/baselines/sankey_circular_simple2.png b/test/image/baselines/sankey_circular_simple2.png
index 61e8c1571e3..7fd506cf2b5 100644
Binary files a/test/image/baselines/sankey_circular_simple2.png and b/test/image/baselines/sankey_circular_simple2.png differ
diff --git a/test/image/baselines/sankey_energy.png b/test/image/baselines/sankey_energy.png
index 0e059234f0b..36641bd909c 100644
Binary files a/test/image/baselines/sankey_energy.png and b/test/image/baselines/sankey_energy.png differ
diff --git a/test/image/baselines/sankey_energy_dark.png b/test/image/baselines/sankey_energy_dark.png
index b2feea2325f..3a9b1f4d922 100644
Binary files a/test/image/baselines/sankey_energy_dark.png and b/test/image/baselines/sankey_energy_dark.png differ
diff --git a/test/image/baselines/sankey_groups.png b/test/image/baselines/sankey_groups.png
index ee05b7d512e..41ea4caeec5 100644
Binary files a/test/image/baselines/sankey_groups.png and b/test/image/baselines/sankey_groups.png differ
diff --git a/test/image/baselines/sankey_large_padding.png b/test/image/baselines/sankey_large_padding.png
index b27b24c12eb..e7e70a7b614 100644
Binary files a/test/image/baselines/sankey_large_padding.png and b/test/image/baselines/sankey_large_padding.png differ
diff --git a/test/image/baselines/sankey_link_concentration.png b/test/image/baselines/sankey_link_concentration.png
index 9b512b8fac7..79d5b7e5a98 100644
Binary files a/test/image/baselines/sankey_link_concentration.png and b/test/image/baselines/sankey_link_concentration.png differ
diff --git a/test/image/baselines/sankey_messy.png b/test/image/baselines/sankey_messy.png
index e42539a8a7d..d06586a3833 100644
Binary files a/test/image/baselines/sankey_messy.png and b/test/image/baselines/sankey_messy.png differ
diff --git a/test/image/baselines/sankey_subplots.png b/test/image/baselines/sankey_subplots.png
index 5034eade547..fa0bb6f7f52 100644
Binary files a/test/image/baselines/sankey_subplots.png and b/test/image/baselines/sankey_subplots.png differ
diff --git a/test/image/baselines/sankey_subplots_circular.png b/test/image/baselines/sankey_subplots_circular.png
index 9d744f00ca4..1ee0a9ec92d 100644
Binary files a/test/image/baselines/sankey_subplots_circular.png and b/test/image/baselines/sankey_subplots_circular.png differ
diff --git a/test/image/baselines/sankey_x_y.png b/test/image/baselines/sankey_x_y.png
index f769d35ab57..2b2b9fef300 100644
Binary files a/test/image/baselines/sankey_x_y.png and b/test/image/baselines/sankey_x_y.png differ
diff --git a/test/image/mocks/sankey_energy_dark.json b/test/image/mocks/sankey_energy_dark.json
index 6552a355a34..e7848260609 100644
--- a/test/image/mocks/sankey_energy_dark.json
+++ b/test/image/mocks/sankey_energy_dark.json
@@ -366,6 +366,7 @@
         "height": 1000,
         "paper_bgcolor": "rgba(0,0,0,1)",
         "font": {
+            "color": "white",
             "size": 10
         },
         "updatemenus": [
diff --git a/test/image/mocks/sankey_subplots.json b/test/image/mocks/sankey_subplots.json
index 17cc7202c20..42700358634 100644
--- a/test/image/mocks/sankey_subplots.json
+++ b/test/image/mocks/sankey_subplots.json
@@ -1,40 +1,57 @@
 {
     "data": [{
             "domain": {
-                "x": [0, 0.45]
+                "y": [0, 0.45]
             },
             "type": "sankey",
             "orientation": "h",
             "node": {
                 "line": {
+                    "width": 4,
                     "color": "black"
                 },
-                "label": ["el1", "el2", "el3"]
+                "label": [
+                    "<i>Rien ne se perd,<br>rien ne se crée,<br>tout se <b>transforme</b>.</i><sub>",
+                    "H<sub>2</sub>O",
+                    "<b>e<sup>iπ</sup> = cos i + i sin π</b>"
+                ]
             },
             "link": {
                 "source": [0, 2],
                 "target": [1, 1],
                 "value": [120, 50],
                 "label": ["stram1", "stream2"]
+            },
+            "textfont": {
+                "color": "darkblue"
             }
         },
         {
             "domain": {
-                "x": [0.55, 1]
+                "y": [0.55, 1]
             },
             "type": "sankey",
-            "orientation": "h",
+            "orientation": "v",
             "node": {
+                "thickness": 50,
                 "line": {
+                    "width": 4,
                     "color": "black"
                 },
-                "label": ["el4", "el5", "el6"]
+                "label": [
+                    "<i>Rien ne se perd,<br>rien ne se crée,<br>tout se <b>transforme</b>.</i><sub>",
+                    "H<sub>2</sub>O",
+                    "<b>e<sup>iπ</sup> = cos i + i sin π</b>"
+                ]
             },
             "link": {
                 "source": [0, 2],
                 "target": [1, 1],
                 "value": [120, 50],
-                "label": ["stram4", "stream5"]
+                "label": ["stram1", "stream2"]
+            },
+            "textfont": {
+                "color": "darkblue"
             }
         },
         {
@@ -44,7 +61,8 @@
     ],
     "layout": {
         "title": {"text": "Multiple Sankey plots"},
-        "autosize": true,
+        "width": 600,
+        "height": 600,
         "showlegend": false
     }
 }
diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js
index 2f83014fb6a..e8a25e3465a 100644
--- a/test/jasmine/tests/sankey_test.js
+++ b/test/jasmine/tests/sankey_test.js
@@ -669,7 +669,7 @@ describe('sankey tests', function() {
             Lib.clearThrottle();
         }
 
-        var node = [404, 302];
+        var node = [410, 300];
         var link = [450, 300];
 
         it('should show the correct hover labels', function(done) {
@@ -677,7 +677,7 @@ describe('sankey tests', function() {
             var mockCopy = Lib.extendDeep({}, mock);
 
             Plotly.newPlot(gd, mockCopy).then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -697,7 +697,7 @@ describe('sankey tests', function() {
                 return Plotly.relayout(gd, 'hoverlabel.font.family', 'Roboto');
             })
             .then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -722,7 +722,7 @@ describe('sankey tests', function() {
                 });
             })
             .then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -753,7 +753,7 @@ describe('sankey tests', function() {
                 });
             })
             .then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -815,7 +815,7 @@ describe('sankey tests', function() {
             mockCopy.data[0].link.customdata[61] = ['linkCustomdata0', 'linkCustomdata1'];
 
             Plotly.newPlot(gd, mockCopy).then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -838,7 +838,7 @@ describe('sankey tests', function() {
                 });
             })
             .then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     [ 'hovertemplate', '447TWh', '447.48', 'nodeCustomdata0/nodeCustomdata1', 'trace 0'],
@@ -890,7 +890,7 @@ describe('sankey tests', function() {
 
             Plotly.newPlot(gd, mockCopy)
             .then(function() {
-                _hover(404, 302);
+                _hover(410, 300);
 
                 assertLabel(
                     ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -1054,7 +1054,7 @@ describe('sankey tests', function() {
             var mockCopy = Lib.extendDeep({}, mock);
 
             Plotly.newPlot(gd, mockCopy)
-            .then(function() { _hover(404, 302); })
+            .then(function() { _hover(410, 300); })
             .then(function() {
                 assertHoverLabelContent({
                     nums: 'Solid\nincoming flow count: 4\noutgoing flow count: 3',
@@ -1064,7 +1064,7 @@ describe('sankey tests', function() {
             .then(function() {
                 return Plotly.restyle(gd, 'hoverlabel.namelength', 3);
             })
-            .then(function() { _hover(404, 302); })
+            .then(function() { _hover(410, 300); })
             .then(function() {
                 assertHoverLabelContent({
                     nums: 'Solid\nincoming flow count: 4\noutgoing flow count: 3',
@@ -1086,7 +1086,7 @@ describe('sankey tests', function() {
 
         function _makeWrapper(eventType, mouseFn) {
             var posByElementType = {
-                node: [404, 302],
+                node: [410, 300],
                 link: [450, 300]
             };