diff --git a/src/webgpu/shader/validation/extension/dual_source_blending.spec.ts b/src/webgpu/shader/validation/extension/dual_source_blending.spec.ts
index 7c0b6b8ce856..a8122067462f 100644
--- a/src/webgpu/shader/validation/extension/dual_source_blending.spec.ts
+++ b/src/webgpu/shader/validation/extension/dual_source_blending.spec.ts
@@ -317,3 +317,246 @@ ${kStageIOValidationTests[t.params.attr].shader}
 `;
     t.expectCompileResult(kStageIOValidationTests[t.params.attr].pass, code);
   });
+
+const kUsageValidationTests = {
+  const: {
+    code: `@blend_src(0) const color = 1.2;`,
+    pass: false,
+    use_default_main_function: true,
+  },
+  override: {
+    code: `@blend_src(0) @id(0) override color : f32;`,
+    pass: false,
+    use_default_main_function: true,
+  },
+  let: {
+    code: `
+    @fragment fn main() -> vec4f {
+      let @blend_src(0) color = vec4f();
+      return color;
+    }
+    `,
+    pass: false,
+    use_default_main_function: false,
+  },
+  var_private: {
+    code: `@blend_src(0) var<private> color : vec4f;`,
+    pass: false,
+    use_default_main_function: true,
+  },
+  var_function: {
+    code: `
+    @fragment fn main() -> vec4f {
+      var @blend_src(0) color : vec4f;
+      color = vec4f();
+      return color;
+    }
+    `,
+    pass: false,
+    use_default_main_function: false,
+  },
+  function_declaration: {
+    code: `
+    @blend_src(0) fn fun() {}
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  non_entrypoint_function_input_non_struct: {
+    code: `
+    fn fun(@blend_src(0) color : vec4f) -> vec4f {
+      return color;
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  non_entrypoint_function_output_non_struct: {
+    code: `
+    fn fun() -> @blend_src(0) vec4f {
+      return vec4f();
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  entrypoint_input_non_struct: {
+    code: `
+    @fragment fn main(@location(0) @blend_src(0) color : vec4f) -> @location(0) vec4f {
+      return color;
+    }
+    `,
+    pass: false,
+    use_default_main_function: false,
+  },
+  entrypoint_output_non_struct: {
+    code: `
+    @fragment fn main() -> @location(0) @blend_src(0) vec4f {
+      return vec4f();
+    }
+    `,
+    pass: false,
+    use_default_main_function: false,
+  },
+  struct_member_only_blend_src_0: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_only_blend_src_1: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_no_location_blend_src_0: {
+    code: `
+    struct BlendSrcStruct {
+      @blend_src(0) color : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_no_location_blend_src_1: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_duplicate_blend_src_0: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @location(0) @blend_src(0) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_duplicate_blend_src_1: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(1) color : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_has_non_zero_location_blend_src_0: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color1 : vec4f,
+      @location(1) @blend_src(0) color2 : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_has_non_zero_location_blend_src_1: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @location(0) @blend_src(1) blend1 : vec4f,
+      @location(1) @blend_src(1) blend2 : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_non_zero_location_blend_src_0_blend_src_1: {
+    code: `
+    struct BlendSrcStruct {
+      @location(1) @blend_src(0) color : vec4f,
+      @location(1) @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_has_non_zero_location_no_blend_src: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+      @location(1) color2 : vec4f,
+    }
+    `,
+    pass: false,
+    use_default_main_function: true,
+  },
+  struct_member_no_location_no_blend_src: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+      depth : f32,
+    }
+    `,
+    pass: true,
+    use_default_main_function: true,
+  },
+  struct_member_blend_src_and_builtin: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+      @builtin(frag_depth) depth : f32,
+    }
+    `,
+    pass: true,
+    use_default_main_function: true,
+  },
+  struct_member_location_0_blend_src_0_blend_src_1: {
+    code: `
+    struct BlendSrcStruct {
+      @location(0) @blend_src(0) color : vec4f,
+      @location(0) @blend_src(1) blend : vec4f,
+    }
+    `,
+    pass: true,
+    use_default_main_function: true,
+  },
+};
+
+g.test('blend_src_usage')
+  .desc(
+    `Test that blend_src can only be used on a member of a structure, and must be used together with
+    the location attribute. In addition, if blend_src is used on a member of a structure, there must
+    be exactly 2 members that have location attribute in the structure: one is @location(0)
+    @blend_src(0) and another is @location(0) @blend_src(1).`
+  )
+  .params(u => u.combine('attr', keysOf(kUsageValidationTests)))
+  .beforeAllSubcases(t =>
+    t.selectDeviceOrSkipTestCase({ requiredFeatures: ['dual-source-blending'] })
+  )
+  .fn(t => {
+    const code = `
+enable dual_source_blending;
+
+${kUsageValidationTests[t.params.attr].code}
+
+${
+  kUsageValidationTests[t.params.attr].use_default_main_function
+    ? `@fragment fn main() -> @location(0) vec4f {
+  return vec4f();
+}`
+    : ''
+}
+`;
+    t.expectCompileResult(kUsageValidationTests[t.params.attr].pass, code);
+  });