diff --git a/src/containers/fill-color-indicator.jsx b/src/containers/fill-color-indicator.jsx
index 8ff198ef..43291462 100644
--- a/src/containers/fill-color-indicator.jsx
+++ b/src/containers/fill-color-indicator.jsx
@@ -16,7 +16,7 @@ import {isBitmap} from '../lib/format';
 import GradientTypes from '../lib/gradient-types';
 
 import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
-import {applyFillColorToSelection,
+import {applyColorToSelection,
     applyGradientTypeToSelection,
     getRotatedColor,
     swapColorsInSelection,
@@ -45,11 +45,12 @@ class FillColorIndicator extends React.Component {
     }
     handleChangeFillColor (newColor) {
         // Apply color and update redux, but do not update svg until picker closes.
-        const isDifferent = applyFillColorToSelection(
+        const isDifferent = applyColorToSelection(
             newColor,
             this.props.colorIndex,
             this.props.gradientType === GradientTypes.SOLID,
             isBitmap(this.props.format),
+            false, // applyToStroke
             this.props.textEditTarget);
         this._hasChanged = this._hasChanged || isDifferent;
         this.props.onChangeFillColor(newColor, this.props.colorIndex);
@@ -59,6 +60,7 @@ class FillColorIndicator extends React.Component {
         const isDifferent = applyGradientTypeToSelection(
             gradientType,
             isBitmap(this.props.format),
+            false, // applyToStroke
             this.props.textEditTarget);
         this._hasChanged = this._hasChanged || isDifferent;
         const hasSelectedItems = getSelectedLeafItems().length > 0;
@@ -92,6 +94,7 @@ class FillColorIndicator extends React.Component {
         if (getSelectedLeafItems().length) {
             const isDifferent = swapColorsInSelection(
                 isBitmap(this.props.format),
+                false, // applyToStroke
                 this.props.textEditTarget);
             this.props.setSelectedItems();
             this._hasChanged = this._hasChanged || isDifferent;
diff --git a/src/containers/stroke-color-indicator.jsx b/src/containers/stroke-color-indicator.jsx
index ea78e0cb..57f68e3e 100644
--- a/src/containers/stroke-color-indicator.jsx
+++ b/src/containers/stroke-color-indicator.jsx
@@ -10,7 +10,7 @@ import Formats from '../lib/format';
 import {isBitmap} from '../lib/format';
 
 import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
-import {applyStrokeColorToSelection, applyStrokeWidthToSelection} from '../helper/style-path';
+import {applyColorToSelection, applyStrokeWidthToSelection} from '../helper/style-path';
 
 class StrokeColorIndicator extends React.Component {
     constructor (props) {
@@ -40,8 +40,13 @@ class StrokeColorIndicator extends React.Component {
             this.props.onChangeStrokeWidth(0);
         }
         // Apply color and update redux, but do not update svg until picker closes.
-        this._hasChanged =
-            applyStrokeColorToSelection(newColor, isBitmap(this.props.format), this.props.textEditTarget) ||
+        this._hasChanged = applyColorToSelection(
+            newColor,
+            0, // colorIndex,
+            true, // isSolidGradient
+            isBitmap(this.props.format),
+            true, // applyToStroke
+            this.props.textEditTarget) ||
             this._hasChanged;
         this.props.onChangeStrokeColor(newColor);
     }
diff --git a/src/containers/stroke-width-indicator.jsx b/src/containers/stroke-width-indicator.jsx
index d224f44d..16412677 100644
--- a/src/containers/stroke-width-indicator.jsx
+++ b/src/containers/stroke-width-indicator.jsx
@@ -7,7 +7,7 @@ import {changeStrokeColor} from '../reducers/stroke-color';
 import {changeStrokeWidth} from '../reducers/stroke-width';
 import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx';
 import {getSelectedLeafItems} from '../helper/selection';
-import {applyStrokeColorToSelection, applyStrokeWidthToSelection, getColorsFromSelection, MIXED}
+import {applyColorToSelection, applyStrokeWidthToSelection, getColorsFromSelection, MIXED}
     from '../helper/style-path';
 import Modes from '../lib/modes';
 import Formats from '../lib/format';
@@ -25,7 +25,13 @@ class StrokeWidthIndicator extends React.Component {
         if ((!this.props.strokeWidth || this.props.strokeWidth === 0) && newWidth > 0) {
             let currentColor = getColorsFromSelection(getSelectedLeafItems(), isBitmap(this.props.format)).strokeColor;
             if (currentColor === null) {
-                changed = applyStrokeColorToSelection('#000', isBitmap(this.props.format), this.props.textEditTarget) ||
+                changed = applyColorToSelection(
+                    '#000',
+                    0, // colorIndex,
+                    true, // isSolidGradient
+                    isBitmap(this.props.format),
+                    true, // applyToStroke
+                    this.props.textEditTarget) ||
                     changed;
                 currentColor = '#000';
             } else if (currentColor !== MIXED) {
diff --git a/src/helper/style-path.js b/src/helper/style-path.js
index 1a2a394a..33cadf46 100644
--- a/src/helper/style-path.js
+++ b/src/helper/style-path.js
@@ -7,13 +7,13 @@ import GradientTypes from '../lib/gradient-types';
 import parseColor from 'parse-color';
 import {DEFAULT_COLOR} from '../reducers/fill-style';
 import {isCompoundPathChild} from '../helper/compound-path';
+import log from '../log/log';
 
 const MIXED = 'scratch-paint/style-path/mixed';
 
 // Check if the item color matches the incoming color. If the item color is a gradient, we assume
 // that the incoming color never matches, since we don't support gradients yet.
 const _colorMatch = function (itemColor, incomingColor) {
-    // @todo colorMatch should not be called with gradients as arguments once stroke gradients are supported
     if (itemColor && itemColor.type === 'gradient') return false;
     // Either both are null or both are the same color when converted to CSS.
     return (!itemColor && !incomingColor) ||
@@ -62,7 +62,8 @@ const getRotatedColor = function (firstColor) {
  * @param {?string} color2 CSS string, or null for transparent
  * @param {GradientType} gradientType gradient type
  * @param {paper.Rectangle} bounds Bounds of the object
- * @param {paper.Point} radialCenter Where the center of a radial gradient should be, if the gradient is radial
+ * @param {?paper.Point} [radialCenter] Where the center of a radial gradient should be, if the gradient is radial.
+ * Defaults to center of bounds.
  * @return {paper.Color} Color object with gradient, may be null or color string if the gradient type is solid
  */
 const createGradientObject = function (color1, color2, gradientType, bounds, radialCenter) {
@@ -74,7 +75,7 @@ const createGradientObject = function (color1, color2, gradientType, bounds, rad
         color2 = getColorStringForTransparent(color1);
     }
     const halfLongestDimension = Math.max(bounds.width, bounds.height) / 2;
-    const start = gradientType === GradientTypes.RADIAL ? radialCenter :
+    const start = gradientType === GradientTypes.RADIAL ? (radialCenter || bounds.center) :
         gradientType === GradientTypes.VERTICAL ? bounds.topCenter :
             gradientType === GradientTypes.HORIZONTAL ? bounds.leftCenter :
                 null;
@@ -93,17 +94,25 @@ const createGradientObject = function (color1, color2, gradientType, bounds, rad
 };
 
 /**
- * Called when setting fill color
+ * Called when setting an item's color
  * @param {string} colorString color, css format, or null if completely transparent
  * @param {number} colorIndex index of color being changed
  * @param {boolean} isSolidGradient True if is solid gradient. Sometimes the item has a gradient but the color
  *     picker is set to a solid gradient. This happens when a mix of colors and gradient types is selected.
  *     When changing the color in this case, the solid gradient should override the existing gradient on the item.
- * @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode
+ * @param {?boolean} bitmapMode True if the color is being set in bitmap mode
+ * @param {?boolean} applyToStroke True if changing the selection's stroke, false if changing its fill.
  * @param {?string} textEditTargetId paper.Item.id of text editing target, if any
  * @return {boolean} Whether the color application actually changed visibly.
  */
-const applyFillColorToSelection = function (colorString, colorIndex, isSolidGradient, bitmapMode, textEditTargetId) {
+const applyColorToSelection = function (
+    colorString,
+    colorIndex,
+    isSolidGradient,
+    bitmapMode,
+    applyToStroke,
+    textEditTargetId
+) {
     const items = _getColorStateListeners(textEditTargetId);
     let changed = false;
     for (let item of items) {
@@ -112,40 +121,51 @@ const applyFillColorToSelection = function (colorString, colorIndex, isSolidGrad
         }
 
         // In bitmap mode, fill color applies to the stroke if there is a stroke
-        if (bitmapMode && item.strokeColor !== null && item.strokeWidth) {
+        if (
+            bitmapMode &&
+            !applyToStroke &&
+            item.strokeColor !== null &&
+            item.strokeWidth
+        ) {
             if (!_colorMatch(item.strokeColor, colorString)) {
                 changed = true;
                 item.strokeColor = colorString;
             }
-        } else if (isSolidGradient || !item.fillColor || !item.fillColor.gradient ||
-                !item.fillColor.gradient.stops.length === 2) {
+            continue;
+        }
+
+        const itemColorProp = applyToStroke ? 'strokeColor' : 'fillColor';
+        const itemColor = item[itemColorProp];
+
+        if (isSolidGradient || !itemColor || !itemColor.gradient ||
+                !itemColor.gradient.stops.length === 2) {
             // Applying a solid color
-            if (!_colorMatch(item.fillColor, colorString)) {
+            if (!_colorMatch(itemColor, colorString)) {
                 changed = true;
                 if (isPointTextItem(item) && !colorString) {
                     // Allows transparent text to be hit
-                    item.fillColor = 'rgba(0,0,0,0)';
+                    item[itemColorProp] = 'rgba(0,0,0,0)';
                 } else {
-                    item.fillColor = colorString;
+                    item[itemColorProp] = colorString;
                 }
             }
-        } else if (!_colorMatch(item.fillColor.gradient.stops[colorIndex].color, colorString)) {
+        } else if (!_colorMatch(itemColor.gradient.stops[colorIndex].color, colorString)) {
             // Changing one color of an existing gradient
             changed = true;
             const otherIndex = colorIndex === 0 ? 1 : 0;
             if (colorString === null) {
-                colorString = getColorStringForTransparent(item.fillColor.gradient.stops[otherIndex].color.toCSS());
+                colorString = getColorStringForTransparent(itemColor.gradient.stops[otherIndex].color.toCSS());
             }
             const colors = [0, 0];
             colors[colorIndex] = colorString;
             // If the other color is transparent, its RGB values need to be adjusted for the gradient to be smooth
-            if (item.fillColor.gradient.stops[otherIndex].color.alpha === 0) {
+            if (itemColor.gradient.stops[otherIndex].color.alpha === 0) {
                 colors[otherIndex] = getColorStringForTransparent(colorString);
             } else {
-                colors[otherIndex] = item.fillColor.gradient.stops[otherIndex].color.toCSS();
+                colors[otherIndex] = itemColor.gradient.stops[otherIndex].color.toCSS();
             }
             // There seems to be a bug where setting colors on stops doesn't always update the view, so set gradient.
-            item.fillColor.gradient = {stops: colors, radial: item.fillColor.gradient.radial};
+            itemColor.gradient = {stops: colors, radial: itemColor.gradient.radial};
         }
     }
     return changed;
@@ -154,10 +174,13 @@ const applyFillColorToSelection = function (colorString, colorIndex, isSolidGrad
 /**
  * Called to swap gradient colors
  * @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode
+ * @param {?boolean} applyToStroke True if changing the selection's stroke, false if changing its fill.
  * @param {?string} textEditTargetId paper.Item.id of text editing target, if any
  * @return {boolean} Whether the color application actually changed visibly.
  */
-const swapColorsInSelection = function (bitmapMode, textEditTargetId) {
+const swapColorsInSelection = function (bitmapMode, applyToStroke, textEditTargetId) {
+    if (bitmapMode) return; // @todo
+
     const items = _getColorStateListeners(textEditTargetId);
     let changed = false;
     for (const item of items) {
@@ -166,21 +189,19 @@ const swapColorsInSelection = function (bitmapMode, textEditTargetId) {
         // that would leave us right where we started.
         if (isCompoundPathChild(item)) continue;
 
-        if (bitmapMode) {
-            // @todo
-            return;
-        } else if (!item.fillColor || !item.fillColor.gradient || !item.fillColor.gradient.stops.length === 2) {
+        const itemColor = applyToStroke ? item.strokeColor : item.fillColor;
+        if (!itemColor || !itemColor.gradient || !itemColor.gradient.stops.length === 2) {
             // Only one color; nothing to swap
             continue;
-        } else if (!item.fillColor.gradient.stops[0].color.equals(item.fillColor.gradient.stops[1].color)) {
+        } else if (!itemColor.gradient.stops[0].color.equals(itemColor.gradient.stops[1].color)) {
             // Changing one color of an existing gradient
             changed = true;
             const colors = [
-                item.fillColor.gradient.stops[1].color.toCSS(),
-                item.fillColor.gradient.stops[0].color.toCSS()
+                itemColor.gradient.stops[1].color.toCSS(),
+                itemColor.gradient.stops[0].color.toCSS()
             ];
             // There seems to be a bug where setting colors on stops doesn't always update the view, so set gradient.
-            item.fillColor.gradient = {stops: colors, radial: item.fillColor.gradient.radial};
+            itemColor.gradient = {stops: colors, radial: itemColor.gradient.radial};
         }
     }
     return changed;
@@ -190,10 +211,11 @@ const swapColorsInSelection = function (bitmapMode, textEditTargetId) {
  * Called when setting gradient type
  * @param {GradientType} gradientType gradient type
  * @param {?boolean} bitmapMode True if the fill color is being set in bitmap mode
+ * @param {boolean} applyToStroke True if changing the selection's stroke, false if changing its fill.
  * @param {?string} textEditTargetId paper.Item.id of text editing target, if any
  * @return {boolean} Whether the color application actually changed visibly.
  */
-const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEditTargetId) {
+const applyGradientTypeToSelection = function (gradientType, bitmapMode, applyToStroke, textEditTargetId) {
     const items = _getColorStateListeners(textEditTargetId);
     let changed = false;
     for (let item of items) {
@@ -201,40 +223,45 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi
             item = item.parent;
         }
 
+        const itemColorProp = applyToStroke ? 'strokeColor' : 'fillColor';
+        const itemColor = item[itemColorProp];
+
+        const hasGradient = itemColor && itemColor.gradient;
+
         let itemColor1;
-        if (item.fillColor === null || item.fillColor.alpha === 0) {
+        if (itemColor === null || itemColor.alpha === 0) {
             // Transparent
             itemColor1 = null;
-        } else if (!item.fillColor.gradient) {
+        } else if (!hasGradient) {
             // Solid color
-            itemColor1 = item.fillColor.toCSS();
-        } else if (!item.fillColor.gradient.stops[0] || item.fillColor.gradient.stops[0].color.alpha === 0) {
+            itemColor1 = itemColor.toCSS();
+        } else if (!itemColor.gradient.stops[0] || itemColor.gradient.stops[0].color.alpha === 0) {
             // Gradient where first color is transparent
             itemColor1 = null;
         } else {
             // Gradient where first color is not transparent
-            itemColor1 = item.fillColor.gradient.stops[0].color.toCSS();
+            itemColor1 = itemColor.gradient.stops[0].color.toCSS();
         }
 
         let itemColor2;
-        if (!item.fillColor || !item.fillColor.gradient || !item.fillColor.gradient.stops[1]) {
+        if (!hasGradient || !itemColor.gradient.stops[1]) {
             // If item color is solid or a gradient that has no 2nd color, set the 2nd color based on the first color
             itemColor2 = getRotatedColor(itemColor1);
-        } else if (item.fillColor.gradient.stops[1].color.alpha === 0) {
+        } else if (itemColor.gradient.stops[1].color.alpha === 0) {
             // Gradient has 2nd color which is transparent
             itemColor2 = null;
         } else {
             // Gradient has 2nd color which is not transparent
-            itemColor2 = item.fillColor.gradient.stops[1].color.toCSS();
+            itemColor2 = itemColor.gradient.stops[1].color.toCSS();
         }
 
         if (bitmapMode) {
             // @todo Add when we apply gradients to selections in bitmap mode
             continue;
         } else if (gradientType === GradientTypes.SOLID) {
-            if (item.fillColor && item.fillColor.gradient) {
+            if (itemColor && itemColor.gradient) {
                 changed = true;
-                item.fillColor = itemColor1;
+                item[itemColorProp] = itemColor1;
             }
             continue;
         }
@@ -245,12 +272,15 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi
         if (itemColor2 === null) {
             itemColor2 = getColorStringForTransparent(itemColor1);
         }
-        if (gradientType === GradientTypes.RADIAL) {
-            const hasRadialGradient = item.fillColor && item.fillColor.gradient && item.fillColor.gradient.radial;
+
+        switch (gradientType) {
+        case GradientTypes.RADIAL: {
+            const hasRadialGradient = hasGradient && itemColor.gradient.radial;
             if (!hasRadialGradient) {
                 changed = true;
                 const halfLongestDimension = Math.max(item.bounds.width, item.bounds.height) / 2;
-                item.fillColor = {
+
+                item[itemColorProp] = {
                     gradient: {
                         stops: [itemColor1, itemColor2],
                         radial: true
@@ -259,13 +289,15 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi
                     destination: item.position.add(new paper.Point(halfLongestDimension, 0))
                 };
             }
-        } else if (gradientType === GradientTypes.HORIZONTAL) {
-            const hasHorizontalGradient = item.fillColor && item.fillColor.gradient &&
-                !item.fillColor.gradient.radial &&
-                Math.abs(item.fillColor.origin.y - item.fillColor.destination.y) < 1e-8;
+            break;
+        }
+        case GradientTypes.HORIZONTAL: {
+            const hasHorizontalGradient = hasGradient && !itemColor.gradient.radial &&
+                Math.abs(itemColor.origin.y - itemColor.destination.y) < 1e-8;
             if (!hasHorizontalGradient) {
                 changed = true;
-                item.fillColor = {
+
+                item[itemColorProp] = {
                     gradient: {
                         stops: [itemColor1, itemColor2]
                     },
@@ -273,12 +305,15 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi
                     destination: item.bounds.rightCenter
                 };
             }
-        } else if (gradientType === GradientTypes.VERTICAL) {
-            const hasVerticalGradient = item.fillColor && item.fillColor.gradient && !item.fillColor.gradient.radial &&
-                Math.abs(item.fillColor.origin.x - item.fillColor.destination.x) < 1e-8;
+            break;
+        }
+        case GradientTypes.VERTICAL: {
+            const hasVerticalGradient = hasGradient && !itemColor.gradient.radial &&
+                Math.abs(itemColor.origin.x - itemColor.destination.x) < 1e-8;
             if (!hasVerticalGradient) {
                 changed = true;
-                item.fillColor = {
+
+                item[itemColorProp] = {
                     gradient: {
                         stops: [itemColor1, itemColor2]
                     },
@@ -286,31 +321,8 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, textEdi
                     destination: item.bounds.bottomCenter
                 };
             }
+            break;
         }
-    }
-    return changed;
-};
-
-/**
- * Called when setting stroke color
- * @param {string} colorString New color, css format
- * @param {?boolean} bitmapMode True if the stroke color is being set in bitmap mode
- * @param {?string} textEditTargetId paper.Item.id of text editing target, if any
- * @return {boolean} Whether the color application actually changed visibly.
- */
-const applyStrokeColorToSelection = function (colorString, bitmapMode, textEditTargetId) {
-    // Bitmap mode doesn't have stroke color
-    if (bitmapMode) return false;
-
-    const items = _getColorStateListeners(textEditTargetId);
-    let changed = false;
-    for (let item of items) {
-        if (item.parent instanceof paper.CompoundPath) {
-            item = item.parent;
-        }
-        if (!_colorMatch(item.strokeColor, colorString)) {
-            changed = true;
-            item.strokeColor = colorString;
         }
     }
     return changed;
@@ -339,6 +351,33 @@ const applyStrokeWidthToSelection = function (value, textEditTargetId) {
     return changed;
 };
 
+const _colorStateFromGradient = gradient => {
+    const colorState = {};
+    // Scratch only recognizes 2 color gradients
+    if (gradient.stops.length === 2) {
+        if (gradient.radial) {
+            colorState.gradientType = GradientTypes.RADIAL;
+        } else {
+            // Always use horizontal for linear gradients, since horizontal and vertical gradients
+            // are the same with rotation. We don't want to show MIXED just because anything is rotated.
+            colorState.gradientType = GradientTypes.HORIZONTAL;
+        }
+        colorState.primary = gradient.stops[0].color.alpha === 0 ?
+            null :
+            gradient.stops[0].color.toCSS();
+        colorState.secondary = gradient.stops[1].color.alpha === 0 ?
+            null :
+            gradient.stops[1].color.toCSS();
+    } else {
+        if (gradient.stops.length < 2) log.warn(`Gradient has ${gradient.stops.length} stop(s)`);
+
+        colorState.primary = MIXED;
+        colorState.secondary = MIXED;
+    }
+
+    return colorState;
+};
+
 /**
  * Get state of colors and stroke width for selection
  * @param {!Array<paper.Item>} selectedItems Selected paper items
@@ -349,12 +388,15 @@ const applyStrokeWidthToSelection = function (value, textEditTargetId) {
  *     Thickness is line thickness, used in the bitmap editor
  */
 const getColorsFromSelection = function (selectedItems, bitmapMode) {
+    // TODO: DRY out this code
     let selectionFillColorString;
     let selectionFillColor2String;
     let selectionStrokeColorString;
+    let selectionStrokeColor2String;
     let selectionStrokeWidth;
     let selectionThickness;
-    let selectionGradientType;
+    let selectionFillGradientType;
+    let selectionStrokeGradientType;
     let firstChild = true;
 
     for (let item of selectedItems) {
@@ -365,7 +407,9 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
         let itemFillColorString;
         let itemFillColor2String;
         let itemStrokeColorString;
-        let itemGradientType = GradientTypes.SOLID;
+        let itemStrokeColor2String;
+        let itemFillGradientType = GradientTypes.SOLID;
+        let itemStrokeGradientType = GradientTypes.SOLID;
 
         if (!isGroup(item)) {
             if (item.fillColor) {
@@ -373,25 +417,10 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
                 if (isPointTextItem(item) && item.fillColor.alpha === 0) {
                     itemFillColorString = null;
                 } else if (item.fillColor.type === 'gradient') {
-                    // Scratch only recognizes 2 color gradients
-                    if (item.fillColor.gradient.stops.length === 2) {
-                        if (item.fillColor.gradient.radial) {
-                            itemGradientType = GradientTypes.RADIAL;
-                        } else {
-                            // Always use horizontal for linear gradients, since horizontal and vertical gradients
-                            // are the same with rotation. We don't want to show MIXED just because anything is rotated.
-                            itemGradientType = GradientTypes.HORIZONTAL;
-                        }
-                        itemFillColorString = item.fillColor.gradient.stops[0].color.alpha === 0 ?
-                            null :
-                            item.fillColor.gradient.stops[0].color.toCSS();
-                        itemFillColor2String = item.fillColor.gradient.stops[1].color.alpha === 0 ?
-                            null :
-                            item.fillColor.gradient.stops[1].color.toCSS();
-                    } else {
-                        itemFillColorString = MIXED;
-                        itemFillColor2String = MIXED;
-                    }
+                    const {primary, secondary, gradientType} = _colorStateFromGradient(item.fillColor.gradient);
+                    itemFillColorString = primary;
+                    itemFillColor2String = secondary;
+                    itemFillGradientType = gradientType;
                 } else {
                     itemFillColorString = item.fillColor.alpha === 0 ?
                         null :
@@ -399,15 +428,34 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
                 }
             }
             if (item.strokeColor) {
-                // Stroke color is fill color in bitmap
-                if (bitmapMode) {
-                    itemFillColorString = item.strokeColor.toCSS();
-                } else if (item.strokeColor.type === 'gradient') {
-                    itemStrokeColorString = MIXED;
+
+                if (item.strokeColor.type === 'gradient') {
+                    const {primary, secondary, gradientType} = _colorStateFromGradient(item.strokeColor.gradient);
+                    const strokeColorString = primary;
+                    const strokeColor2String = secondary;
+                    const strokeGradientType = gradientType;
+
+                    // Stroke color is fill color in bitmap
+                    if (bitmapMode) {
+                        itemFillColorString = strokeColorString;
+                        itemFillColor2String = strokeColor2String;
+                        itemFillGradientType = strokeGradientType;
+                    } else {
+                        itemStrokeColorString = strokeColorString;
+                        itemStrokeColor2String = strokeColor2String;
+                        itemStrokeGradientType = strokeGradientType;
+                    }
                 } else {
-                    itemStrokeColorString = item.strokeColor.alpha === 0 || !item.strokeWidth ?
+                    const strokeColorString = item.strokeColor.alpha === 0 || !item.strokeWidth ?
                         null :
                         item.strokeColor.toCSS();
+
+                    // Stroke color is fill color in bitmap
+                    if (bitmapMode) {
+                        itemFillColorString = strokeColorString;
+                    } else {
+                        itemStrokeColorString = strokeColorString;
+                    }
                 }
             } else {
                 itemStrokeColorString = null;
@@ -418,7 +466,9 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
                 selectionFillColorString = itemFillColorString;
                 selectionFillColor2String = itemFillColor2String;
                 selectionStrokeColorString = itemStrokeColorString;
-                selectionGradientType = itemGradientType;
+                selectionStrokeColor2String = itemStrokeColor2String;
+                selectionFillGradientType = itemFillGradientType;
+                selectionStrokeGradientType = itemStrokeGradientType;
                 selectionStrokeWidth = itemStrokeColorString ? item.strokeWidth : 0;
                 if (item.strokeWidth && item.data && item.data.zoomLevel) {
                     selectionThickness = item.strokeWidth / item.data.zoomLevel;
@@ -430,14 +480,22 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
             if (itemFillColor2String !== selectionFillColor2String) {
                 selectionFillColor2String = MIXED;
             }
-            if (itemGradientType !== selectionGradientType) {
-                selectionGradientType = GradientTypes.SOLID;
+            if (itemFillGradientType !== selectionFillGradientType) {
+                selectionFillGradientType = GradientTypes.SOLID;
                 selectionFillColorString = MIXED;
                 selectionFillColor2String = MIXED;
             }
+            if (itemStrokeGradientType !== selectionStrokeGradientType) {
+                selectionStrokeGradientType = GradientTypes.SOLID;
+                selectionStrokeColorString = MIXED;
+                selectionStrokeColor2String = MIXED;
+            }
             if (itemStrokeColorString !== selectionStrokeColorString) {
                 selectionStrokeColorString = MIXED;
             }
+            if (itemStrokeColor2String !== selectionStrokeColor2String) {
+                selectionStrokeColor2String = MIXED;
+            }
             const itemStrokeWidth = itemStrokeColorString ? item.strokeWidth : 0;
             if (selectionStrokeWidth !== itemStrokeWidth) {
                 selectionStrokeWidth = null;
@@ -445,27 +503,50 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
         }
     }
     // Convert selection gradient type from horizontal to vertical if first item is exactly vertical
-    if (selectedItems && selectedItems.length && selectionGradientType !== GradientTypes.SOLID) {
+    // This is because up to this point, we assume all non-radial gradients are horizontal
+    // Otherwise, if there were a mix of horizontal/vertical gradient types in the selection, they would show as MIXED
+    // whereas we want them to show as horizontal (or vertical if the first item is vertical)
+    if (selectedItems && selectedItems.length) {
         let firstItem = selectedItems[0];
         if (firstItem.parent instanceof paper.CompoundPath) firstItem = firstItem.parent;
-        const direction = firstItem.fillColor.destination.subtract(firstItem.fillColor.origin);
-        if (Math.abs(direction.angle) === 90) {
-            selectionGradientType = GradientTypes.VERTICAL;
+
+        if (selectionFillGradientType !== GradientTypes.SOLID) {
+            // Stroke color is fill color in bitmap if fill color is missing
+            // TODO: this whole "treat horizontal/vertical gradients specially" logic is janky; refactor at some point
+            const firstItemColor = (bitmapMode && firstItem.strokeColor) ? firstItem.strokeColor : firstItem.fillColor;
+            const direction = firstItemColor.destination.subtract(firstItemColor.origin);
+            if (Math.abs(direction.angle) === 90) {
+                selectionFillGradientType = GradientTypes.VERTICAL;
+            }
+        }
+
+        if (selectionStrokeGradientType !== GradientTypes.SOLID) {
+            const direction = firstItem.strokeColor.destination.subtract(firstItem.strokeColor.origin);
+            if (Math.abs(direction.angle) === 90) {
+                selectionStrokeGradientType = GradientTypes.VERTICAL;
+            }
         }
     }
     if (bitmapMode) {
         return {
             fillColor: selectionFillColorString ? selectionFillColorString : null,
             fillColor2: selectionFillColor2String ? selectionFillColor2String : null,
-            gradientType: selectionGradientType,
+            fillGradientType: selectionFillGradientType,
             thickness: selectionThickness
         };
     }
+
+    // Treat stroke gradients as MIXED
+    // TODO: remove this once stroke gradients are supported
+    if (selectionStrokeGradientType !== GradientTypes.SOLID) selectionStrokeColorString = MIXED;
+
     return {
         fillColor: selectionFillColorString ? selectionFillColorString : null,
         fillColor2: selectionFillColor2String ? selectionFillColor2String : null,
-        gradientType: selectionGradientType,
+        fillGradientType: selectionFillGradientType,
         strokeColor: selectionStrokeColorString ? selectionStrokeColorString : null,
+        strokeColor2: selectionStrokeColor2String ? selectionStrokeColor2String : null,
+        strokeGradientType: selectionStrokeGradientType,
         strokeWidth: selectionStrokeWidth || (selectionStrokeWidth === null) ? selectionStrokeWidth : 0
     };
 };
@@ -510,9 +591,8 @@ const styleShape = function (path, options) {
 };
 
 export {
-    applyFillColorToSelection,
+    applyColorToSelection,
     applyGradientTypeToSelection,
-    applyStrokeColorToSelection,
     applyStrokeWidthToSelection,
     createGradientObject,
     getColorsFromSelection,
diff --git a/src/helper/tools/text-tool.js b/src/helper/tools/text-tool.js
index d7188846..bba8bda7 100644
--- a/src/helper/tools/text-tool.js
+++ b/src/helper/tools/text-tool.js
@@ -241,7 +241,8 @@ class TextTool extends paper.Tool {
                 content: '',
                 font: this.font,
                 fontSize: 40,
-                // TODO: style using gradient?
+                // TODO: style using gradient
+                // https://github.com/LLK/scratch-paint/issues/1164
                 fillColor: this.colorState.fillColor.primary,
                 // Default leading for both the HTML text area and paper.PointText
                 // is 120%, but for some reason they are slightly off from each other.
diff --git a/src/reducers/fill-style.js b/src/reducers/fill-style.js
index b98b73f8..98046c49 100644
--- a/src/reducers/fill-style.js
+++ b/src/reducers/fill-style.js
@@ -14,7 +14,7 @@ const reducer = makeColorStyleReducer({
     defaultColor: DEFAULT_COLOR,
     selectionPrimaryColorKey: 'fillColor',
     selectionSecondaryColorKey: 'fillColor2',
-    selectionGradientTypeKey: 'gradientType'
+    selectionGradientTypeKey: 'fillGradientType'
 });
 
 // Action creators ==================================