diff a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java
--- a/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/text/Text.java
@@ -80,10 +80,11 @@
 import javafx.css.CssMetaData;
 import javafx.css.FontCssMetaData;
 import javafx.css.Styleable;
 import javafx.css.StyleableBooleanProperty;
 import javafx.css.StyleableDoubleProperty;
+import javafx.css.StyleableIntegerProperty;
 import javafx.css.StyleableObjectProperty;
 import javafx.css.StyleableProperty;
 import javafx.geometry.BoundingBox;
 import javafx.geometry.Bounds;
 import javafx.geometry.NodeOrientation;
@@ -305,10 +306,11 @@
             if (getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
                 layout.setDirection(TextLayout.DIRECTION_RTL);
             } else {
                 layout.setDirection(TextLayout.DIRECTION_LTR);
             }
+            layout.setTabSize(getTabSize());
         }
         return layout;
     }
 
     private GlyphList[] textRuns = null;
@@ -1265,22 +1267,58 @@
             y = getYAdjustment(getVisualBounds()) + (float)getY();
         }
         return TransformedShape.translatedShape(shape, x, y);
     }
 
+    /**
+     * The size of a tab stop in spaces.
+     *
+     * @return the {@code tabSize} property
+     *
+     * @defaultValue {@code 8}
+     *
+     * @since 14
+     */
+    public final IntegerProperty tabSizeProperty() {
+        return getTextAttribute().tabSizeProperty();
+    }
+
+    /**
+     * Gets the size of a tab stop in spaces.
+     * @return the size of a tab in spaces
+     * @since 14
+     */
+    public final int getTabSize() {
+        if (attributes == null || attributes.tabSize == null) {
+            return TextLayout.DEFAULT_TAB_SIZE;
+        }
+        return getTextAttribute().getTabSize();
+    }
+
+    /**
+     * Sets the size of a tab stop.
+     * @param spaces the size of a tab in spaces. Defaults to 8.
+     * Minimum is 1, lower values will be clamped to 1.
+     * @since 14
+     */
+    public final void setTabSize(int spaces) {
+        tabSizeProperty().set(spaces);
+    }
+
+
    /***************************************************************************
     *                                                                         *
     *                            Stylesheet Handling                          *
     *                                                                         *
     **************************************************************************/
 
     /*
      * Super-lazy instantiation pattern from Bill Pugh.
      */
-     private static class StyleableProperties {
+    private static class StyleableProperties {
 
-         private static final CssMetaData<Text,Font> FONT =
+        private static final CssMetaData<Text,Font> FONT =
             new FontCssMetaData<Text>("-fx-font", Font.getDefault()) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.font == null || !node.font.isBound();
@@ -1288,15 +1326,15 @@
 
             @Override
             public StyleableProperty<Font> getStyleableProperty(Text node) {
                 return (StyleableProperty<Font>)node.fontProperty();
             }
-         };
+        };
 
-         private static final CssMetaData<Text,Boolean> UNDERLINE =
+        private static final CssMetaData<Text,Boolean> UNDERLINE =
             new CssMetaData<Text,Boolean>("-fx-underline",
-                 BooleanConverter.getInstance(), Boolean.FALSE) {
+                BooleanConverter.getInstance(), Boolean.FALSE) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.attributes == null ||
                        node.attributes.underline == null ||
@@ -1305,15 +1343,15 @@
 
             @Override
             public StyleableProperty<Boolean> getStyleableProperty(Text node) {
                 return (StyleableProperty<Boolean>)node.underlineProperty();
             }
-         };
+        };
 
-         private static final CssMetaData<Text,Boolean> STRIKETHROUGH =
+        private static final CssMetaData<Text,Boolean> STRIKETHROUGH =
             new CssMetaData<Text,Boolean>("-fx-strikethrough",
-                 BooleanConverter.getInstance(), Boolean.FALSE) {
+                BooleanConverter.getInstance(), Boolean.FALSE) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.attributes == null ||
                        node.attributes.strikethrough == null ||
@@ -1322,17 +1360,17 @@
 
             @Override
             public StyleableProperty<Boolean> getStyleableProperty(Text node) {
                 return (StyleableProperty<Boolean>)node.strikethroughProperty();
             }
-         };
+        };
 
-         private static final
-             CssMetaData<Text,TextAlignment> TEXT_ALIGNMENT =
-                 new CssMetaData<Text,TextAlignment>("-fx-text-alignment",
-                 new EnumConverter<TextAlignment>(TextAlignment.class),
-                 TextAlignment.LEFT) {
+        private static final
+            CssMetaData<Text,TextAlignment> TEXT_ALIGNMENT =
+                new CssMetaData<Text,TextAlignment>("-fx-text-alignment",
+                new EnumConverter<TextAlignment>(TextAlignment.class),
+                TextAlignment.LEFT) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.attributes == null ||
                        node.attributes.textAlignment == null ||
@@ -1341,16 +1379,16 @@
 
             @Override
             public StyleableProperty<TextAlignment> getStyleableProperty(Text node) {
                 return (StyleableProperty<TextAlignment>)node.textAlignmentProperty();
             }
-         };
+        };
 
-         private static final CssMetaData<Text,VPos> TEXT_ORIGIN =
-                 new CssMetaData<Text,VPos>("-fx-text-origin",
-                 new EnumConverter<VPos>(VPos.class),
-                 VPos.BASELINE) {
+        private static final CssMetaData<Text,VPos> TEXT_ORIGIN =
+                new CssMetaData<Text,VPos>("-fx-text-origin",
+                new EnumConverter<VPos>(VPos.class),
+                VPos.BASELINE) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.attributes == null ||
                        node.attributes.textOrigin == null ||
@@ -1359,18 +1397,18 @@
 
             @Override
             public StyleableProperty<VPos> getStyleableProperty(Text node) {
                 return (StyleableProperty<VPos>)node.textOriginProperty();
             }
-         };
+        };
 
-         private static final CssMetaData<Text,FontSmoothingType>
-             FONT_SMOOTHING_TYPE =
-             new CssMetaData<Text,FontSmoothingType>(
-                 "-fx-font-smoothing-type",
-                 new EnumConverter<FontSmoothingType>(FontSmoothingType.class),
-                 FontSmoothingType.GRAY) {
+        private static final CssMetaData<Text,FontSmoothingType>
+            FONT_SMOOTHING_TYPE =
+            new CssMetaData<Text,FontSmoothingType>(
+                "-fx-font-smoothing-type",
+                new EnumConverter<FontSmoothingType>(FontSmoothingType.class),
+                FontSmoothingType.GRAY) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.fontSmoothingType == null ||
                        !node.fontSmoothingType.isBound();
@@ -1380,16 +1418,16 @@
             public StyleableProperty<FontSmoothingType>
                                  getStyleableProperty(Text node) {
 
                 return (StyleableProperty<FontSmoothingType>)node.fontSmoothingTypeProperty();
             }
-         };
+        };
 
-         private static final
-             CssMetaData<Text,Number> LINE_SPACING =
-                 new CssMetaData<Text,Number>("-fx-line-spacing",
-                 SizeConverter.getInstance(), 0) {
+        private static final
+            CssMetaData<Text,Number> LINE_SPACING =
+                new CssMetaData<Text,Number>("-fx-line-spacing",
+                SizeConverter.getInstance(), 0) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.attributes == null ||
                        node.attributes.lineSpacing == null ||
@@ -1398,44 +1436,62 @@
 
             @Override
             public StyleableProperty<Number> getStyleableProperty(Text node) {
                 return (StyleableProperty<Number>)node.lineSpacingProperty();
             }
-         };
+        };
 
-         private static final CssMetaData<Text, TextBoundsType>
-             BOUNDS_TYPE =
-             new CssMetaData<Text,TextBoundsType>(
-                 "-fx-bounds-type",
-                 new EnumConverter<TextBoundsType>(TextBoundsType.class),
-                 DEFAULT_BOUNDS_TYPE) {
+        private static final CssMetaData<Text, TextBoundsType>
+            BOUNDS_TYPE =
+            new CssMetaData<Text,TextBoundsType>(
+                "-fx-bounds-type",
+                new EnumConverter<TextBoundsType>(TextBoundsType.class),
+                DEFAULT_BOUNDS_TYPE) {
 
             @Override
             public boolean isSettable(Text node) {
                 return node.boundsType == null || !node.boundsType.isBound();
             }
 
             @Override
             public StyleableProperty<TextBoundsType> getStyleableProperty(Text node) {
                 return (StyleableProperty<TextBoundsType>)node.boundsTypeProperty();
             }
-         };
+        };
+
+        private static final CssMetaData<Text,Number> TAB_SIZE =
+                new CssMetaData<Text,Number>("-fx-tab-size",
+                SizeConverter.getInstance(), TextLayout.DEFAULT_TAB_SIZE) {
+
+            @Override
+            public boolean isSettable(Text node) {
+                return node.attributes == null ||
+                       node.attributes.tabSize == null ||
+                      !node.attributes.tabSize.isBound();
+            }
 
-     private final static List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
-         static {
+            @Override
+            public StyleableProperty<Number> getStyleableProperty(Text node) {
+                return (StyleableProperty<Number>)node.tabSizeProperty();
+            }
+        };
+
+    private final static List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
+        static {
             final List<CssMetaData<? extends Styleable, ?>> styleables =
                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Shape.getClassCssMetaData());
             styleables.add(FONT);
             styleables.add(UNDERLINE);
             styleables.add(STRIKETHROUGH);
             styleables.add(TEXT_ALIGNMENT);
             styleables.add(TEXT_ORIGIN);
             styleables.add(FONT_SMOOTHING_TYPE);
             styleables.add(LINE_SPACING);
             styleables.add(BOUNDS_TYPE);
+            styleables.add(TAB_SIZE);
             STYLEABLES = Collections.unmodifiableList(styleables);
-         }
+        }
     }
 
     /**
      * @return The CssMetaData associated with this class, which may include the
      * CssMetaData of its superclasses.
@@ -1819,10 +1875,40 @@
                 caretBias =
                         new SimpleBooleanProperty(Text.this, "caretBias", DEFAULT_CARET_BIAS);
             }
             return caretBias;
         }
+
+        private IntegerProperty tabSize;
+
+        final int getTabSize() {
+            return tabSize == null ? TextLayout.DEFAULT_TAB_SIZE : tabSize.get();
+        }
+
+        final IntegerProperty tabSizeProperty() {
+            if (tabSize == null) {
+                tabSize = new StyleableIntegerProperty(TextLayout.DEFAULT_TAB_SIZE) {
+                    @Override public Object getBean() { return Text.this; }
+                    @Override public String getName() { return "tabSize"; }
+                    @Override public CssMetaData getCssMetaData() {
+                        return StyleableProperties.TAB_SIZE;
+                    }
+                    @Override public void set(int v) { super.set((v < 1) ? 1 : v); }
+                    @Override protected void invalidated() {
+                        TextLayout layout = getTextLayout();
+                        if (layout.setTabSize(get())) {
+                            needsTextLayout();
+                        }
+                        NodeHelper.markDirty(Text.this, DirtyBits.TEXT_ATTRS);
+                        if (getBoundsType() == TextBoundsType.VISUAL) {
+                            NodeHelper.geomChanged(Text.this);
+                        }
+                    }
+                };
+            }
+            return tabSize;
+        }
     }
 
     /**
      * Returns a string representation of this {@code Text} object.
      * @return a string representation of this {@code Text} object.
