diff --git a/build/gen-hanzi-data/stroke-type.ts b/build/gen-hanzi-data/stroke-type.ts
index 6e35b9984b12904222212acf92cfdf1af9b6d4ec..e6e3b93e15466e201437f847248f2a9422a484e4 100644
--- a/build/gen-hanzi-data/stroke-type.ts
+++ b/build/gen-hanzi-data/stroke-type.ts
@@ -3,7 +3,7 @@ import cncharOrder from 'cnchar-order'
 import cncharTrad from 'cnchar-trad'
 import {
   type StrokeTypeMaybeUnclean,
-  type StrokeType
+  StrokeType
 } from '../../src-common/stroke-encodings'
 import { strokeTypeOverrides } from './stroke-type-overrides'
 import { compat } from './blocks/index'
@@ -34,7 +34,11 @@ export function queryStrokeTypes (
   if (override !== undefined) {
     return override
   }
-  return queryCncharStrokeTypes(compatChar)
+  let strokes = queryCncharStrokeTypes(compatChar)
+  if (expectedLen !== undefined) {
+    strokes = addGrassHengIfMissing(expectedLen, strokes)
+  }
+  return strokes
 }
 
 function queryCncharStrokeTypes (char: string): StrokeType[] {
@@ -42,9 +46,43 @@ function queryCncharStrokeTypes (char: string): StrokeType[] {
   if (!Array.isArray(strokeShapes)) {
     return []
   }
-  // if cnchar has multiple stroke types, only keep first, and map to a closed
-  // set of strokes without duplicate stroke kinds
-  return strokeShapes.map(t => normalizeStroke(
-    t.split('|')[0] as StrokeTypeMaybeUnclean
-  ))
+  return strokeShapes.map(t =>
+    // if cnchar has multiple stroke types, only keep first, and map to a closed
+    // set of strokes without duplicate stroke kinds
+    normalizeStroke(
+      t.split('|')[0] as StrokeTypeMaybeUnclean
+    )
+  )
+}
+
+const traditionalGrassStrokes = [
+  StrokeType.SHU,
+  StrokeType.HENG,
+  StrokeType.SHU,
+  StrokeType.HENG
+]
+
+/**
+ * If the character has one stroke less than expected, and the first strokes
+ * are consistent with a simplified grass radical, then return a new array that
+ * starts with the four strokes of a traditional grass radical and then
+ * continues with the given strokes starting from the fourth stroke.
+ *
+ * If the length is not one short by one, or if the start does not look like a
+ * simplified grass radical, then return the given strokes array unchanged.
+ */
+function addGrassHengIfMissing (
+  expectedLen: number,
+  strokes: StrokeType[]
+): StrokeType[] {
+  const applyPatch =
+    // HanziWriter has one more stroke
+    (strokes.length + 1) === expectedLen &&
+    // and the strokes look like the simplified version
+    strokes[0] === StrokeType.HENG &&
+    strokes[1] === StrokeType.SHU &&
+    strokes[2] === StrokeType.SHU
+  return applyPatch
+    ? [...traditionalGrassStrokes, ...strokes.slice(3)]
+    : strokes
 }
diff --git a/test/data/stroke-type.test.ts b/test/data/stroke-type.test.ts
index c8da210a36aeebbfde101cb357325d7a558f86ee..14cbb7c608b5846d9a051cb37b32e413f39ddcd8 100644
--- a/test/data/stroke-type.test.ts
+++ b/test/data/stroke-type.test.ts
@@ -1,6 +1,9 @@
 import { StrokeType } from '../../src-common/stroke-encodings'
 import { queryStrokeTypes } from '../../build/gen-hanzi-data/stroke-type'
 import { cjkRadicals, kangxiRadicals } from './radicals'
+import {
+  queryPatchedHanziWriterData
+} from '../../build/gen-hanzi-data/patched-hanzi-writer'
 
 describe('Kangxi radicals', () => {
   describe('have non-empty strokes', () => {
@@ -53,3 +56,38 @@ describe('CJK Radicals Supplement radicals', () => {
     expect(queryStrokeTypes(char)).toEqual(strokes)
   }
 })
+
+describe('Traditional characters with grass radical at the start', () => {
+  const chars = [
+    '葉',
+    '蓮',
+    '夢',
+    '觀',
+    '歡',
+    '莊',
+    '藥',
+    '蓋',
+    '藝',
+    '華',
+    '蔭',
+    '舊',
+    '蘭',
+    '蘋',
+    '蕩',
+    '蔣',
+    '蕭',
+    '薑'
+  ]
+  for (const char of chars) {
+    test(char, async () => {
+      const hanziWriterCount = (await queryPatchedHanziWriterData(char))
+        .strokes.length
+      expect(queryStrokeTypes(char, hanziWriterCount).slice(0, 4)).toEqual([
+        StrokeType.SHU,
+        StrokeType.HENG,
+        StrokeType.SHU,
+        StrokeType.HENG
+      ])
+    })
+  }
+})