diff --git a/args.go b/args.go
index ed1e70ce..08f92f1c 100644
--- a/args.go
+++ b/args.go
@@ -129,3 +129,16 @@ func MatchAll(pargs ...PositionalArgs) PositionalArgs {
 func ExactValidArgs(n int) PositionalArgs {
 	return MatchAll(ExactArgs(n), OnlyValidArgs)
 }
+
+// WithUsage wraps another PositionalArgs function. If the function fails
+// (returns a non-nil error), the error is supplemented with the command's
+// usage message, providing the user with the information required to run the
+// command correctly.
+func WithUsage(wrapped PositionalArgs) PositionalArgs {
+	return func(cmd *Command, args []string) error {
+		if err := wrapped(cmd, args); err != nil {
+			return fmt.Errorf("%w\n\n%s", err, cmd.UsageString())
+		}
+		return nil
+	}
+}
diff --git a/args_test.go b/args_test.go
index 90d174cc..ec17fdc7 100644
--- a/args_test.go
+++ b/args_test.go
@@ -539,3 +539,42 @@ func TestLegacyArgsSubcmdAcceptsArgs(t *testing.T) {
 		t.Fatalf("Unexpected error: %v", err)
 	}
 }
+
+// WithUsage
+
+func TestWithUsage(t *testing.T) {
+	c := getCommand(WithUsage(ExactArgs(1)), false)
+
+	_, err := executeCommand(c /* no args */)
+	if err == nil {
+		t.Fatalf("Expected error, got nil")
+	}
+
+	got, want := err.Error(), c.UsageString()
+	if !strings.Contains(got, want) {
+		t.Errorf("Expected error containing %q, got %q", want, got)
+	}
+}
+
+func ExampleWithUsage() {
+	cmd := &Command{
+		Use:  "example <arg>",
+		Args: WithUsage(ExactArgs(1)),
+		Run: func(*Command, []string) {
+			panic("not reached")
+		},
+	}
+
+	cmd.SetArgs([]string{"1", "2"})
+	err := cmd.Execute()
+	fmt.Print(err)
+
+	// Output:
+	// accepts 1 arg(s), received 2
+	//
+	// Usage:
+	//   example <arg> [flags]
+	//
+	// Flags:
+	//   -h, --help   help for example
+}