Repository: clap-rs/clap (language: rust)

Issue:
Please support a feature to omit all help and messages
For the purposes of reducing binary size, I'd love to have a new default feature flag for `clap` (that projects could then disable) that would omit generated `--help` and all friendly error/constraint messages, and minimize error reporting to just "Invalid arguments" or similar. That would help for embedded use cases that may want to reduce the sizes of binaries used in scripting, without regard to human invocations of those binaries.


Discussion / hints:
Related to #1485 
Closing in favor of #1365 
This may be a side effect of that but not directly related. Reopening 
I would like this as well, but supported as a runtime option instead of a feature flag, so that it can be enabled or disabled on a case-by-case basis.
> I would like this as well, but supported as a runtime option instead of a feature flag, so that it can be enabled or disabled on a case-by-case basis.

@Rua This issue is mainly about reducing binary size. If you just wanna control the help message emission in the runtime, the right-now approach is using [try_get_matches](https://docs.rs/clap/3.0.0-beta.2/clap/struct.App.html#method.try_get_matches) to get a result and control the output by yourself. 
Postponing this for later than 3.0 because we need design on how to divided the existing `help`, error messages, usage etc.. into separate feature flags.
https://github.com/clap-rs/clap/issues/2914 would unblock code-generating a display-agnostic(wrapping, styling) help message 

So breaking this down, parts we can trim down
- Simpler error messages
- Static help (`App::override_help`)
- Code-genned formatted help with re-rendering the help (built on #2914)
- Non-smart usage
- Static usage (`App::override_usage`)

The main question I have is how should we divide this up among feature flags without overwhelming people (with too many flags).  For example, a user could want to provide codegenned formatted help but still get all of the other nice features.  Next would be the bike shedding over the names.

To get the conversation started, I'll start with
- `helpgen`
  - Without `App::override_help`, no `--help` flag is generated
- `detailed-errors`
  - includes smart usage, people have to disable `helpgen` to get rid of smart usage

With that said, how we divide things up I think depends partly on the cost of each feature.  For example, if smart usage is more expensive, then maybe that should be broken out into its own feature flag.  Or if detailed errors isn't too expensive, maybe we don't provide it.  So I am assuming whatever decision we come up is a guideline and the implementer would need to measure the results and adapt accordingly.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d1cbdae452..a739c5bcb2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -268,6 +268,7 @@ Behavior Changes
 - Allow resetting most builder methods
 - Can now pass runtime generated data to `Command`, `Arg`, `ArgGroup`, `PossibleValue`, etc without managing lifetimes with the `string` feature flag (#2150, #4223)
 - *(error)* `Error::apply` for changing the formatter for dropping binary size (#4111)
+- *(error)* New default `error-context` feature flag that can be turned off for smaller binaries
 - *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312)
 - *(help)* New `{tab}` variable for `Command::help_template` (#4161)
 
diff --git a/Cargo.toml b/Cargo.toml
index b18b4ca1f61..2071d2707f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -57,6 +57,9 @@ pre-release-replacements = [
 default = [
   "std",
   "color",
+  "help",
+  "usage",
+  "error-context",
   "suggestions",
 ]
 debug = ["clap_derive/debug", "dep:backtrace"] # Enables debug messages
@@ -65,7 +68,10 @@ unstable-doc = ["derive", "cargo", "wrap_help", "env", "unicode", "string", "uns
 # Used in default
 std = [] # support for no_std in a backwards-compatible way
 color = ["dep:atty", "dep:termcolor"]
-suggestions = ["dep:strsim"]
+help = []
+usage = []
+error-context = []
+suggestions = ["dep:strsim", "error-context"]
 
 # Optional
 deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default)
@@ -151,6 +157,7 @@ path = "examples/multicall-hostname.rs"
 [[example]]
 name = "repl"
 path = "examples/repl.rs"
+required-features = ["help"]
 
 [[example]]
 name = "01_quick"
diff --git a/clap_bench/Cargo.toml b/clap_bench/Cargo.toml
index 49f307827cd..ce158b2fee0 100644
--- a/clap_bench/Cargo.toml
+++ b/clap_bench/Cargo.toml
@@ -11,7 +11,7 @@ publish = false
 release = false
 
 [dev-dependencies]
-clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std"] }
+clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "help"] }
 criterion = "0.3.2"
 lazy_static = "1"
 
diff --git a/clap_complete/Cargo.toml b/clap_complete/Cargo.toml
index 50fe07a8af2..838bdbd8adf 100644
--- a/clap_complete/Cargo.toml
+++ b/clap_complete/Cargo.toml
@@ -52,7 +52,7 @@ unicode-xid = { version = "0.2.2", optional = true }
 snapbox = { version = "0.3", features = ["diff"] }
 # Cutting out `filesystem` feature
 trycmd = { version = "0.13", default-features = false, features = ["color-auto", "diff", "examples"] }
-clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "derive"] }
+clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "derive", "help"] }
 
 [[example]]
 name = "dynamic"
diff --git a/clap_complete_fig/Cargo.toml b/clap_complete_fig/Cargo.toml
index dffb1227143..c32ade4c08b 100644
--- a/clap_complete_fig/Cargo.toml
+++ b/clap_complete_fig/Cargo.toml
@@ -44,3 +44,4 @@ clap_complete = { path = "../clap_complete", version = "4.0.0-alpha.0" }
 
 [dev-dependencies]
 snapbox = { version = "0.3", features = ["diff"] }
+clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "help"] }
diff --git a/clap_mangen/Cargo.toml b/clap_mangen/Cargo.toml
index bd73c601966..bed0cafd1bf 100644
--- a/clap_mangen/Cargo.toml
+++ b/clap_mangen/Cargo.toml
@@ -45,7 +45,7 @@ clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, feat
 
 [dev-dependencies]
 snapbox = { version = "0.3", features = ["diff"] }
-clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std"] }
+clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "help"] }
 
 [features]
 default = []
diff --git a/src/_features.rs b/src/_features.rs
index 7c12ef1510c..12d7e536b03 100644
--- a/src/_features.rs
+++ b/src/_features.rs
@@ -6,6 +6,9 @@
 //!
 //! * **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner.
 //! * **color**: Turns on colored error messages.
+//! * **help**: Auto-generate help output
+//! * **usage**: Auto-generate usage
+//! * **error-context**: Include contextual information for errors (which arg failed, etc)
 //! * **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos.
 //!
 //! #### Optional features
diff --git a/src/builder/action.rs b/src/builder/action.rs
index 5a4a3c493de..7cd3e017908 100644
--- a/src/builder/action.rs
+++ b/src/builder/action.rs
@@ -2,7 +2,8 @@
 ///
 /// # Examples
 ///
-/// ```rust
+#[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+#[cfg_attr(feature = "help", doc = " ```")]
 /// # use clap::Command;
 /// # use clap::Arg;
 /// let cmd = Command::new("mycmd")
@@ -211,7 +212,8 @@ pub enum ArgAction {
     ///
     /// # Examples
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::Command;
     /// # use clap::Arg;
     /// let cmd = Command::new("mycmd")
diff --git a/src/builder/arg.rs b/src/builder/arg.rs
index 73ead55d881..dce3794b206 100644
--- a/src/builder/arg.rs
+++ b/src/builder/arg.rs
@@ -1107,7 +1107,8 @@ impl Arg {
     /// # ;
     /// ```
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("config")
@@ -1168,7 +1169,8 @@ impl Arg {
     ///     .value_names(["fast", "slow"]);
     /// ```
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("io")
@@ -1991,7 +1993,8 @@ impl Arg {
     /// Setting `help` displays a short message to the side of the argument when the user passes
     /// `-h` or `--help` (by default).
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -2040,7 +2043,8 @@ impl Arg {
     /// Setting `help` displays a short message to the side of the argument when the user passes
     /// `-h` or `--help` (by default).
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -2096,7 +2100,8 @@ impl Arg {
     ///
     /// # Examples
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg, ArgAction};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("a") // Typically args are grouped alphabetically by name.
@@ -2162,7 +2167,8 @@ impl Arg {
     ///
     /// # Examples
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg, ArgAction};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("opt")
@@ -2212,7 +2218,8 @@ impl Arg {
     ///
     /// Setting `Hidden` will hide the argument when displaying help text
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -2385,7 +2392,8 @@ impl Arg {
     ///
     /// Setting `hide_short_help(true)` will hide the argument when displaying short help text
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -2411,7 +2419,8 @@ impl Arg {
     ///
     /// However, when --help is called
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -2456,7 +2465,8 @@ impl Arg {
     ///
     /// Setting `hide_long_help(true)` will hide the argument when displaying long help text
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -2482,7 +2492,8 @@ impl Arg {
     ///
     /// However, when -h is called
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("cfg")
@@ -4074,10 +4085,6 @@ impl Arg {
         }
     }
 
-    pub(crate) fn longest_filter(&self) -> bool {
-        self.is_takes_value_set() || self.long.is_some() || self.short.is_none()
-    }
-
     // Used for positionals when printing
     pub(crate) fn name_no_brackets(&self) -> String {
         debug!("Arg::name_no_brackets:{}", self.get_id());
@@ -4200,6 +4207,7 @@ impl Arg {
         self.is_multiple_values_set() || matches!(*self.get_action(), ArgAction::Append)
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn get_display_order(&self) -> usize {
         self.disp_ord.unwrap_or(999)
     }
diff --git a/src/builder/command.rs b/src/builder/command.rs
index a400f13ed0d..def9139452b 100644
--- a/src/builder/command.rs
+++ b/src/builder/command.rs
@@ -1,3 +1,5 @@
+#![cfg_attr(not(feature = "usage"), allow(unused_mut))]
+
 // Std
 use std::env;
 use std::ffi::OsString;
@@ -19,7 +21,7 @@ use crate::error::ErrorKind;
 use crate::error::Result as ClapResult;
 use crate::mkeymap::MKeyMap;
 use crate::output::fmt::Stream;
-use crate::output::{fmt::Colorizer, Help, Usage};
+use crate::output::{fmt::Colorizer, write_help, Usage};
 use crate::parser::{ArgMatcher, ArgMatches, Parser};
 use crate::util::ChildGraph;
 use crate::util::FlatMap;
@@ -90,6 +92,7 @@ pub struct Command {
     disp_ord: Option<usize>,
     term_w: Option<usize>,
     max_w: Option<usize>,
+    #[cfg(feature = "help")]
     template: Option<StyledStr>,
     settings: AppFlags,
     g_settings: AppFlags,
@@ -723,7 +726,7 @@ impl Command {
 
         let mut styled = StyledStr::new();
         let usage = Usage::new(self);
-        Help::new(&mut styled, self, &usage, false).write_help();
+        write_help(&mut styled, self, &usage, false);
 
         let c = Colorizer::new(Stream::Stdout, color).with_content(styled);
         c.print()
@@ -750,7 +753,7 @@ impl Command {
 
         let mut styled = StyledStr::new();
         let usage = Usage::new(self);
-        Help::new(&mut styled, self, &usage, true).write_help();
+        write_help(&mut styled, self, &usage, true);
 
         let c = Colorizer::new(Stream::Stdout, color).with_content(styled);
         c.print()
@@ -777,7 +780,7 @@ impl Command {
 
         let mut styled = StyledStr::new();
         let usage = Usage::new(self);
-        Help::new(&mut styled, self, &usage, false).write_help();
+        write_help(&mut styled, self, &usage, false);
         write!(w, "{}", styled)?;
         w.flush()
     }
@@ -803,7 +806,7 @@ impl Command {
 
         let mut styled = StyledStr::new();
         let usage = Usage::new(self);
-        Help::new(&mut styled, self, &usage, true).write_help();
+        write_help(&mut styled, self, &usage, true);
         write!(w, "{}", styled)?;
         w.flush()
     }
@@ -867,10 +870,10 @@ impl Command {
     /// println!("{}", cmd.render_usage());
     /// ```
     pub fn render_usage(&mut self) -> String {
-        self.render_usage_().to_string()
+        self.render_usage_().unwrap_or_default().to_string()
     }
 
-    pub(crate) fn render_usage_(&mut self) -> StyledStr {
+    pub(crate) fn render_usage_(&mut self) -> Option<StyledStr> {
         // If there are global arguments, or settings we need to propagate them down to subcommands
         // before parsing incase we run into a subcommand
         self._build_self(false);
@@ -1727,6 +1730,7 @@ impl Command {
     /// [`Command::before_help`]: Command::before_help()
     /// [`Command::before_long_help`]: Command::before_long_help()
     #[must_use]
+    #[cfg(feature = "help")]
     pub fn help_template(mut self, s: impl IntoResettable<StyledStr>) -> Self {
         self.template = s.into_resettable().into_option();
         self
@@ -2536,7 +2540,8 @@ impl Command {
     ///
     /// # Examples
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, };
     /// let m = Command::new("cust-ord")
     ///     .subcommand(Command::new("alpha") // typically subcommands are grouped
@@ -3139,6 +3144,7 @@ impl Command {
 /// # Reflection
 impl Command {
     #[inline]
+    #[cfg(feature = "usage")]
     pub(crate) fn get_usage_name(&self) -> Option<&str> {
         self.usage_name.as_deref()
     }
@@ -3661,14 +3667,17 @@ impl Command {
         self.help_str.as_ref()
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn get_help_template(&self) -> Option<&StyledStr> {
         self.template.as_ref()
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn get_term_width(&self) -> Option<usize> {
         self.term_w
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn get_max_term_width(&self) -> Option<usize> {
         self.max_w
     }
@@ -3753,6 +3762,11 @@ impl Command {
                 self.settings.insert(AppSettings::DisableHelpFlag.into());
                 self.settings.insert(AppSettings::DisableVersionFlag.into());
             }
+            if !cfg!(feature = "help") && self.get_override_help().is_none() {
+                self.settings.insert(AppSettings::DisableHelpFlag.into());
+                self.settings
+                    .insert(AppSettings::DisableHelpSubcommand.into());
+            }
             if self.is_set(AppSettings::ArgsNegateSubcommands) {
                 self.settings
                     .insert(AppSettings::SubcommandsNegateReqs.into());
@@ -3840,6 +3854,7 @@ impl Command {
         use std::fmt::Write;
 
         let mut mid_string = String::from(" ");
+        #[cfg(feature = "usage")]
         if !self.is_subcommand_negates_reqs_set() && !self.is_args_conflicts_with_subcommands_set()
         {
             let reqs = Usage::new(self).get_required_usage_from(&[], None, true); // maybe Some(m)
@@ -3925,6 +3940,7 @@ impl Command {
 
         if !self.is_set(AppSettings::BinNameBuilt) {
             let mut mid_string = String::from(" ");
+            #[cfg(feature = "usage")]
             if !self.is_subcommand_negates_reqs_set()
                 && !self.is_args_conflicts_with_subcommands_set()
             {
@@ -4279,22 +4295,11 @@ impl<'a, T> Captures<'a> for T {}
 // Internal Query Methods
 impl Command {
     /// Iterate through the *flags* & *options* arguments.
+    #[cfg(any(feature = "usage", feature = "help"))]
     pub(crate) fn get_non_positionals(&self) -> impl Iterator<Item = &Arg> {
         self.get_arguments().filter(|a| !a.is_positional())
     }
 
-    /// Iterate through the *positionals* that don't have custom heading.
-    pub(crate) fn get_positionals_with_no_heading(&self) -> impl Iterator<Item = &Arg> {
-        self.get_positionals()
-            .filter(|a| a.get_help_heading().is_none())
-    }
-
-    /// Iterate through the *flags* & *options* that don't have custom heading.
-    pub(crate) fn get_non_positionals_with_no_heading(&self) -> impl Iterator<Item = &Arg> {
-        self.get_non_positionals()
-            .filter(|a| a.get_help_heading().is_none())
-    }
-
     pub(crate) fn find(&self, arg_id: &Id) -> Option<&Arg> {
         self.args.args().find(|a| a.id == *arg_id)
     }
@@ -4319,6 +4324,7 @@ impl Command {
         self.get_positionals().next().is_some()
     }
 
+    #[cfg(any(feature = "usage", feature = "help"))]
     pub(crate) fn has_visible_subcommands(&self) -> bool {
         self.subcommands
             .iter()
@@ -4473,6 +4479,7 @@ impl Command {
             .map(|sc| sc.get_name())
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn get_display_order(&self) -> usize {
         self.disp_ord.unwrap_or(999)
     }
@@ -4488,7 +4495,7 @@ impl Command {
         let usage = Usage::new(self);
 
         let mut styled = StyledStr::new();
-        Help::new(&mut styled, self, &usage, use_long).write_help();
+        write_help(&mut styled, self, &usage, use_long);
 
         styled
     }
@@ -4565,6 +4572,7 @@ impl Default for Command {
             disp_ord: Default::default(),
             term_w: Default::default(),
             max_w: Default::default(),
+            #[cfg(feature = "help")]
             template: Default::default(),
             settings: Default::default(),
             g_settings: Default::default(),
diff --git a/src/builder/debug_asserts.rs b/src/builder/debug_asserts.rs
index 92ac824ec31..e40d9369e97 100644
--- a/src/builder/debug_asserts.rs
+++ b/src/builder/debug_asserts.rs
@@ -337,6 +337,7 @@ pub(crate) fn assert_app(cmd: &Command) {
 
     _verify_positionals(cmd);
 
+    #[cfg(feature = "help")]
     if let Some(help_template) = cmd.get_help_template() {
         assert!(
             !help_template.to_string().contains("{flags}"),
diff --git a/src/builder/possible_value.rs b/src/builder/possible_value.rs
index 5fbca9f9ed4..03964fe118b 100644
--- a/src/builder/possible_value.rs
+++ b/src/builder/possible_value.rs
@@ -1,5 +1,3 @@
-use std::{borrow::Cow, iter};
-
 use crate::builder::IntoResettable;
 use crate::builder::Str;
 use crate::builder::StyledStr;
@@ -157,6 +155,7 @@ impl PossibleValue {
     /// Get the help specified for this argument, if any and the argument
     /// value is not hidden
     #[inline]
+    #[cfg(feature = "help")]
     pub(crate) fn get_visible_help(&self) -> Option<&StyledStr> {
         if !self.hide {
             self.get_help()
@@ -178,7 +177,8 @@ impl PossibleValue {
 
     /// Get the name if argument value is not hidden, `None` otherwise,
     /// but wrapped in quotes if it contains whitespace
-    pub(crate) fn get_visible_quoted_name(&self) -> Option<Cow<'_, str>> {
+    #[cfg(feature = "help")]
+    pub(crate) fn get_visible_quoted_name(&self) -> Option<std::borrow::Cow<'_, str>> {
         if !self.hide {
             Some(if self.name.contains(char::is_whitespace) {
                 format!("{:?}", self.name).into()
@@ -194,7 +194,7 @@ impl PossibleValue {
     ///
     /// Namely the name and all aliases.
     pub fn get_name_and_aliases(&self) -> impl Iterator<Item = &str> + '_ {
-        iter::once(self.get_name()).chain(self.aliases.iter().map(|s| s.as_str()))
+        std::iter::once(self.get_name()).chain(self.aliases.iter().map(|s| s.as_str()))
     }
 
     /// Tests if the value is valid for this argument value
diff --git a/src/builder/styled_str.rs b/src/builder/styled_str.rs
index 503bf333747..5f2969e0405 100644
--- a/src/builder/styled_str.rs
+++ b/src/builder/styled_str.rs
@@ -1,18 +1,27 @@
-use crate::output::display_width;
-use crate::output::textwrap;
-
 /// Terminal-styling container
 #[derive(Clone, Default, Debug, PartialEq, Eq)]
 pub struct StyledStr {
+    #[cfg(feature = "color")]
     pieces: Vec<(Option<Style>, String)>,
+    #[cfg(not(feature = "color"))]
+    pieces: String,
 }
 
 impl StyledStr {
     /// Create an empty buffer
+    #[cfg(feature = "color")]
     pub const fn new() -> Self {
         Self { pieces: Vec::new() }
     }
 
+    /// Create an empty buffer
+    #[cfg(not(feature = "color"))]
+    pub const fn new() -> Self {
+        Self {
+            pieces: String::new(),
+        }
+    }
+
     pub(crate) fn header(&mut self, msg: impl Into<String>) {
         self.stylize_(Some(Style::Header), msg.into());
     }
@@ -29,6 +38,7 @@ impl StyledStr {
         self.stylize_(Some(Style::Good), msg.into());
     }
 
+    #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
     pub(crate) fn warning(&mut self, msg: impl Into<String>) {
         self.stylize_(Some(Style::Warning), msg.into());
     }
@@ -56,82 +66,113 @@ impl StyledStr {
     }
 
     pub(crate) fn trim_start(&mut self) {
-        if let Some((_, item)) = self.pieces.first_mut() {
+        if let Some((_, item)) = self.iter_mut().next() {
             *item = item.trim_start().to_owned();
         }
     }
 
+    #[cfg(feature = "color")]
     pub(crate) fn trim_end(&mut self) {
         if let Some((_, item)) = self.pieces.last_mut() {
             *item = item.trim_end().to_owned();
         }
     }
 
-    pub(crate) fn replace_newline(&mut self) {
-        for (_, content) in &mut self.pieces {
-            *content = content.replace("{n}", "\n");
-        }
+    #[cfg(not(feature = "color"))]
+    pub(crate) fn trim_end(&mut self) {
+        self.pieces = self.pieces.trim_end().to_owned();
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
-        if let Some((_, first)) = self.pieces.first_mut() {
+        if let Some((_, first)) = self.iter_mut().next() {
             first.insert_str(0, initial);
         }
         let mut line_sep = "\n".to_owned();
         line_sep.push_str(trailing);
-        for (_, content) in &mut self.pieces {
+        for (_, content) in self.iter_mut() {
             *content = content.replace('\n', &line_sep);
         }
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn wrap(&mut self, hard_width: usize) {
-        let mut wrapper = textwrap::wrap_algorithms::LineWrapper::new(hard_width);
-        for (_, content) in &mut self.pieces {
+        let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
+        for (_, content) in self.iter_mut() {
             let mut total = Vec::new();
             for (i, line) in content.split_inclusive('\n').enumerate() {
                 if 0 < i {
                     // start of a section does not imply newline
                     wrapper.reset();
                 }
-                let line =
-                    textwrap::word_separators::find_words_ascii_space(line).collect::<Vec<_>>();
+                let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
+                    .collect::<Vec<_>>();
                 total.extend(wrapper.wrap(line));
             }
             let total = total.join("");
             *content = total;
         }
-        if let Some((_, last)) = self.pieces.last_mut() {
-            *last = last.trim_end().to_owned();
-        }
+
+        self.trim_end();
     }
 
+    #[cfg(feature = "color")]
     fn stylize_(&mut self, style: Option<Style>, msg: String) {
         if !msg.is_empty() {
             self.pieces.push((style, msg));
         }
     }
 
+    #[cfg(not(feature = "color"))]
+    fn stylize_(&mut self, _style: Option<Style>, msg: String) {
+        self.pieces.push_str(&msg);
+    }
+
     #[inline(never)]
+    #[cfg(feature = "help")]
     pub(crate) fn display_width(&self) -> usize {
         let mut width = 0;
-        for (_, c) in &self.pieces {
-            width += display_width(c);
+        for (_, c) in self.iter() {
+            width += crate::output::display_width(c);
         }
         width
     }
 
+    #[cfg(feature = "help")]
     pub(crate) fn is_empty(&self) -> bool {
         self.pieces.is_empty()
     }
 
+    #[cfg(feature = "color")]
     pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
         self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
     }
 
+    #[cfg(not(feature = "color"))]
+    pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
+        [(None, self.pieces.as_str())].into_iter()
+    }
+
+    #[cfg(feature = "color")]
+    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
+        self.pieces.iter_mut().map(|(s, c)| (*s, c))
+    }
+
+    #[cfg(not(feature = "color"))]
+    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
+        [(None, &mut self.pieces)].into_iter()
+    }
+
+    #[cfg(feature = "color")]
     pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
         self.pieces.into_iter()
     }
 
+    #[cfg(not(feature = "color"))]
+    pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
+        [(None, self.pieces)].into_iter()
+    }
+
     pub(crate) fn extend(
         &mut self,
         other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
@@ -240,7 +281,7 @@ fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
 /// Color-unaware printing. Never uses coloring.
 impl std::fmt::Display for StyledStr {
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        for (_, content) in &self.pieces {
+        for (_, content) in self.iter() {
             std::fmt::Display::fmt(content, f)?;
         }
 
diff --git a/src/error/context.rs b/src/error/context.rs
index dbaeebcb42a..d9921b4e40b 100644
--- a/src/error/context.rs
+++ b/src/error/context.rs
@@ -1,6 +1,7 @@
 /// Semantics for a piece of error information
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 #[non_exhaustive]
+#[cfg(feature = "error-context")]
 pub enum ContextKind {
     /// The cause of the error
     InvalidSubcommand,
@@ -66,6 +67,7 @@ impl std::fmt::Display for ContextKind {
 /// A piece of error information
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[non_exhaustive]
+#[cfg(feature = "error-context")]
 pub enum ContextValue {
     /// [`ContextKind`] is self-sufficient, no additional information needed
     None,
diff --git a/src/error/format.rs b/src/error/format.rs
index 44fc2cb72cd..941a40d6221 100644
--- a/src/error/format.rs
+++ b/src/error/format.rs
@@ -1,9 +1,13 @@
 #![allow(missing_copy_implementations)]
 #![allow(missing_debug_implementations)]
+#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
+#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
 
 use crate::builder::Command;
 use crate::builder::StyledStr;
+#[cfg(feature = "error-context")]
 use crate::error::ContextKind;
+#[cfg(feature = "error-context")]
 use crate::error::ContextValue;
 use crate::error::ErrorKind;
 use crate::output::TAB;
@@ -14,20 +18,37 @@ pub trait ErrorFormatter: Sized {
     fn format_error(error: &crate::Error<Self>) -> StyledStr;
 }
 
-/// No error information reported
+/// Report [`ErrorKind`]
+///
+/// No context is included.
+///
+/// **NOTE:** Consider removing the [`error-context`][crate::_features] default feature if using this to remove all
+/// overhead for [`RichFormatter`].
 #[non_exhaustive]
-pub struct NullFormatter;
+pub struct KindFormatter;
 
-impl ErrorFormatter for NullFormatter {
-    fn format_error(_error: &crate::Error<Self>) -> StyledStr {
-        StyledStr::new()
+impl ErrorFormatter for KindFormatter {
+    fn format_error(error: &crate::Error<Self>) -> StyledStr {
+        let mut styled = StyledStr::new();
+        start_error(&mut styled);
+        if let Some(msg) = error.kind().as_str() {
+            styled.none(msg.to_owned());
+        } else if let Some(source) = error.inner.source.as_ref() {
+            styled.none(source.to_string());
+        } else {
+            styled.none("Unknown cause");
+        }
+        styled.none("\n");
+        styled
     }
 }
 
-/// Dump the error information reported
+/// Dump the error context reported
 #[non_exhaustive]
+#[cfg(feature = "error-context")]
 pub struct RawFormatter;
 
+#[cfg(feature = "error-context")]
 impl ErrorFormatter for RawFormatter {
     fn format_error(error: &crate::Error<Self>) -> StyledStr {
         let mut styled = StyledStr::new();
@@ -59,10 +80,12 @@ impl ErrorFormatter for RawFormatter {
     }
 }
 
-/// Default, rich error format
+/// Richly formatted error context
 #[non_exhaustive]
+#[cfg(feature = "error-context")]
 pub struct RichFormatter;
 
+#[cfg(feature = "error-context")]
 impl ErrorFormatter for RichFormatter {
     fn format_error(error: &crate::Error<Self>) -> StyledStr {
         let mut styled = StyledStr::new();
@@ -95,6 +118,7 @@ fn start_error(styled: &mut StyledStr) {
 }
 
 #[must_use]
+#[cfg(feature = "error-context")]
 fn write_dynamic_context(error: &crate::Error, styled: &mut StyledStr) -> bool {
     match error.kind() {
         ErrorKind::ArgumentConflict => {
diff --git a/src/error/kind.rs b/src/error/kind.rs
index 77bbad4456b..aa316ef30d1 100644
--- a/src/error/kind.rs
+++ b/src/error/kind.rs
@@ -250,7 +250,8 @@ pub enum ErrorKind {
     ///
     /// # Examples
     ///
-    /// ```rust
+    #[cfg_attr(not(feature = "help"), doc = " ```ignore")]
+    #[cfg_attr(feature = "help", doc = " ```")]
     /// # use clap::{Command, Arg, error::ErrorKind};
     /// let result = Command::new("prog")
     ///     .try_get_matches_from(vec!["prog", "--help"]);
diff --git a/src/error/mod.rs b/src/error/mod.rs
index d57f609022a..b27486b7cc5 100644
--- a/src/error/mod.rs
+++ b/src/error/mod.rs
@@ -1,5 +1,11 @@
 //! Error reporting
 
+#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
+#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
+#![cfg_attr(not(feature = "error-context"), allow(unused_variables))]
+#![cfg_attr(not(feature = "error-context"), allow(unused_mut))]
+#![cfg_attr(not(feature = "error-context"), allow(clippy::let_and_return))]
+
 // Std
 use std::{
     borrow::Cow,
@@ -19,17 +25,28 @@ use crate::util::FlatMap;
 use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE};
 use crate::Command;
 
+#[cfg(feature = "error-context")]
 mod context;
 mod format;
 mod kind;
 
+pub use format::ErrorFormatter;
+pub use format::KindFormatter;
+pub use kind::ErrorKind;
+
+#[cfg(feature = "error-context")]
 pub use context::ContextKind;
+#[cfg(feature = "error-context")]
 pub use context::ContextValue;
-pub use format::ErrorFormatter;
-pub use format::NullFormatter;
+#[cfg(feature = "error-context")]
 pub use format::RawFormatter;
+#[cfg(feature = "error-context")]
 pub use format::RichFormatter;
-pub use kind::ErrorKind;
+
+#[cfg(not(feature = "error-context"))]
+pub use KindFormatter as DefaultFormatter;
+#[cfg(feature = "error-context")]
+pub use RichFormatter as DefaultFormatter;
 
 /// Short hand for [`Result`] type
 ///
@@ -41,7 +58,7 @@ pub type Result<T, E = Error> = StdResult<T, E>;
 /// See [`Command::error`] to create an error.
 ///
 /// [`Command::error`]: crate::Command::error
-pub struct Error<F: ErrorFormatter = RichFormatter> {
+pub struct Error<F: ErrorFormatter = DefaultFormatter> {
     inner: Box<ErrorInner>,
     phantom: std::marker::PhantomData<F>,
 }
@@ -49,6 +66,7 @@ pub struct Error<F: ErrorFormatter = RichFormatter> {
 #[derive(Debug)]
 struct ErrorInner {
     kind: ErrorKind,
+    #[cfg(feature = "error-context")]
     context: FlatMap<ContextKind, ContextValue>,
     message: Option<Message>,
     source: Option<Box<dyn error::Error + Send + Sync>>,
@@ -89,12 +107,12 @@ impl<F: ErrorFormatter> Error<F> {
     /// ```rust
     /// # use clap::Command;
     /// # use clap::Arg;
-    /// # use clap::error::NullFormatter;
+    /// # use clap::error::KindFormatter;
     /// let cmd = Command::new("foo")
     ///     .arg(Arg::new("input").required(true));
     /// let matches = cmd
     ///     .try_get_matches_from(["foo", "input.txt"])
-    ///     .map_err(|e| e.apply::<NullFormatter>())
+    ///     .map_err(|e| e.apply::<KindFormatter>())
     ///     .unwrap_or_else(|e| e.exit());
     /// ```
     pub fn apply<EF: ErrorFormatter>(self) -> Error<EF> {
@@ -110,12 +128,14 @@ impl<F: ErrorFormatter> Error<F> {
     }
 
     /// Additional information to further qualify the error
+    #[cfg(feature = "error-context")]
     pub fn context(&self) -> impl Iterator<Item = (ContextKind, &ContextValue)> {
         self.inner.context.iter().map(|(k, v)| (*k, v))
     }
 
     /// Lookup a piece of context
     #[inline(never)]
+    #[cfg(feature = "error-context")]
     pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> {
         self.inner.context.get(&kind)
     }
@@ -181,6 +201,7 @@ impl<F: ErrorFormatter> Error<F> {
         Self {
             inner: Box::new(ErrorInner {
                 kind,
+                #[cfg(feature = "error-context")]
                 context: FlatMap::new(),
                 message: None,
                 source: None,
@@ -231,6 +252,7 @@ impl<F: ErrorFormatter> Error<F> {
 
     /// Does not verify if `ContextKind` is already present
     #[inline(never)]
+    #[cfg(feature = "error-context")]
     pub(crate) fn insert_context_unchecked(
         mut self,
         kind: ContextKind,
@@ -242,6 +264,7 @@ impl<F: ErrorFormatter> Error<F> {
 
     /// Does not verify if `ContextKind` is already present
     #[inline(never)]
+    #[cfg(feature = "error-context")]
     pub(crate) fn extend_context_unchecked<const N: usize>(
         mut self,
         context: [(ContextKind, ContextValue); N],
@@ -270,33 +293,48 @@ impl<F: ErrorFormatter> Error<F> {
         cmd: &Command,
         arg: String,
         mut others: Vec<String>,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
-        let others = match others.len() {
-            0 => ContextValue::None,
-            1 => ContextValue::String(others.pop().unwrap()),
-            _ => ContextValue::Strings(others),
-        };
-        Self::new(ErrorKind::ArgumentConflict)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            let others = match others.len() {
+                0 => ContextValue::None,
+                1 => ContextValue::String(others.pop().unwrap()),
+                _ => ContextValue::Strings(others),
+            };
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (ContextKind::PriorArg, others),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+            ]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self {
         Self::invalid_value(cmd, "".to_owned(), good_vals, arg)
     }
 
-    pub(crate) fn no_equals(cmd: &Command, arg: String, usage: StyledStr) -> Self {
-        Self::new(ErrorKind::NoEquals)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
-                (ContextKind::InvalidArg, ContextValue::String(arg)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+    pub(crate) fn no_equals(cmd: &Command, arg: String, usage: Option<StyledStr>) -> Self {
+        let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err
+                .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn invalid_value(
@@ -306,9 +344,11 @@ impl<F: ErrorFormatter> Error<F> {
         arg: String,
     ) -> Self {
         let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
-        let mut err = Self::new(ErrorKind::InvalidValue)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (ContextKind::InvalidValue, ContextValue::String(bad_val)),
                 (
@@ -316,12 +356,14 @@ impl<F: ErrorFormatter> Error<F> {
                     ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()),
                 ),
             ]);
-        if let Some(suggestion) = suggestion {
-            err = err.insert_context_unchecked(
-                ContextKind::SuggestedValue,
-                ContextValue::String(suggestion),
-            );
+            if let Some(suggestion) = suggestion {
+                err = err.insert_context_unchecked(
+                    ContextKind::SuggestedValue,
+                    ContextValue::String(suggestion),
+                );
+            }
         }
+
         err
     }
 
@@ -330,12 +372,14 @@ impl<F: ErrorFormatter> Error<F> {
         subcmd: String,
         did_you_mean: String,
         name: String,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
         let suggestion = format!("{} -- {}", name, subcmd);
-        Self::new(ErrorKind::InvalidSubcommand)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
                 (
                     ContextKind::SuggestedSubcommand,
@@ -345,60 +389,117 @@ impl<F: ErrorFormatter> Error<F> {
                     ContextKind::SuggestedCommand,
                     ContextValue::String(suggestion),
                 ),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+            ]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
-    pub(crate) fn unrecognized_subcommand(cmd: &Command, subcmd: String, usage: StyledStr) -> Self {
-        Self::new(ErrorKind::InvalidSubcommand)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
-                (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+    pub(crate) fn unrecognized_subcommand(
+        cmd: &Command,
+        subcmd: String,
+        usage: Option<StyledStr>,
+    ) -> Self {
+        let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([(
+                ContextKind::InvalidSubcommand,
+                ContextValue::String(subcmd),
+            )]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn missing_required_argument(
         cmd: &Command,
         required: Vec<String>,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
-        Self::new(ErrorKind::MissingRequiredArgument)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
-                (ContextKind::InvalidArg, ContextValue::Strings(required)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+        let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([(
+                ContextKind::InvalidArg,
+                ContextValue::Strings(required),
+            )]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
-    pub(crate) fn missing_subcommand(cmd: &Command, name: String, usage: StyledStr) -> Self {
-        Self::new(ErrorKind::MissingSubcommand)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
-                (ContextKind::InvalidSubcommand, ContextValue::String(name)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+    pub(crate) fn missing_subcommand(
+        cmd: &Command,
+        name: String,
+        usage: Option<StyledStr>,
+    ) -> Self {
+        let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([(
+                ContextKind::InvalidSubcommand,
+                ContextValue::String(name),
+            )]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
-    pub(crate) fn invalid_utf8(cmd: &Command, usage: StyledStr) -> Self {
-        Self::new(ErrorKind::InvalidUtf8)
-            .with_cmd(cmd)
-            .extend_context_unchecked([(ContextKind::Usage, ContextValue::StyledStr(usage))])
+    pub(crate) fn invalid_utf8(cmd: &Command, usage: Option<StyledStr>) -> Self {
+        let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn too_many_values(
         cmd: &Command,
         val: String,
         arg: String,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
-        Self::new(ErrorKind::TooManyValues)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (ContextKind::InvalidValue, ContextValue::String(val)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+            ]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn too_few_values(
@@ -406,11 +507,13 @@ impl<F: ErrorFormatter> Error<F> {
         arg: String,
         min_vals: usize,
         curr_vals: usize,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
-        Self::new(ErrorKind::TooFewValues)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (
                     ContextKind::MinValues,
@@ -420,8 +523,14 @@ impl<F: ErrorFormatter> Error<F> {
                     ContextKind::ActualNumValues,
                     ContextValue::Number(curr_vals as isize),
                 ),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+            ]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn value_validation(
@@ -429,12 +538,17 @@ impl<F: ErrorFormatter> Error<F> {
         val: String,
         err: Box<dyn error::Error + Send + Sync>,
     ) -> Self {
-        Self::new(ErrorKind::ValueValidation)
-            .set_source(err)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::ValueValidation).set_source(err);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (ContextKind::InvalidValue, ContextValue::String(val)),
-            ])
+            ]);
+        }
+
+        err
     }
 
     pub(crate) fn wrong_number_of_values(
@@ -442,11 +556,13 @@ impl<F: ErrorFormatter> Error<F> {
         arg: String,
         num_vals: usize,
         curr_vals: usize,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
-        Self::new(ErrorKind::WrongNumberOfValues)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+        let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (
                     ContextKind::ExpectedNumValues,
@@ -456,45 +572,69 @@ impl<F: ErrorFormatter> Error<F> {
                     ContextKind::ActualNumValues,
                     ContextValue::Number(curr_vals as isize),
                 ),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+            ]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     pub(crate) fn unknown_argument(
         cmd: &Command,
         arg: String,
         did_you_mean: Option<(String, Option<String>)>,
-        usage: StyledStr,
+        usage: Option<StyledStr>,
     ) -> Self {
-        let mut err = Self::new(ErrorKind::UnknownArgument)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
-                (ContextKind::InvalidArg, ContextValue::String(arg)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ]);
-        if let Some((flag, sub)) = did_you_mean {
-            err = err.insert_context_unchecked(
-                ContextKind::SuggestedArg,
-                ContextValue::String(format!("--{}", flag)),
-            );
-            if let Some(sub) = sub {
+        let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err
+                .extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+            if let Some((flag, sub)) = did_you_mean {
                 err = err.insert_context_unchecked(
-                    ContextKind::SuggestedSubcommand,
-                    ContextValue::String(sub),
+                    ContextKind::SuggestedArg,
+                    ContextValue::String(format!("--{}", flag)),
                 );
+                if let Some(sub) = sub {
+                    err = err.insert_context_unchecked(
+                        ContextKind::SuggestedSubcommand,
+                        ContextValue::String(sub),
+                    );
+                }
             }
         }
+
         err
     }
 
-    pub(crate) fn unnecessary_double_dash(cmd: &Command, arg: String, usage: StyledStr) -> Self {
-        Self::new(ErrorKind::UnknownArgument)
-            .with_cmd(cmd)
-            .extend_context_unchecked([
+    pub(crate) fn unnecessary_double_dash(
+        cmd: &Command,
+        arg: String,
+        usage: Option<StyledStr>,
+    ) -> Self {
+        let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
+
+        #[cfg(feature = "error-context")]
+        {
+            err = err.extend_context_unchecked([
                 (ContextKind::InvalidArg, ContextValue::String(arg)),
                 (ContextKind::TrailingArg, ContextValue::Bool(true)),
-                (ContextKind::Usage, ContextValue::StyledStr(usage)),
-            ])
+            ]);
+            if let Some(usage) = usage {
+                err = err
+                    .insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
+            }
+        }
+
+        err
     }
 
     fn formatted(&self) -> Cow<'_, StyledStr> {
@@ -552,13 +692,13 @@ pub(crate) enum Message {
 }
 
 impl Message {
-    fn format(&mut self, cmd: &Command, usage: StyledStr) {
+    fn format(&mut self, cmd: &Command, usage: Option<StyledStr>) {
         match self {
             Message::Raw(s) => {
                 let mut message = String::new();
                 std::mem::swap(s, &mut message);
 
-                let styled = format::format_error_message(&message, Some(cmd), Some(usage));
+                let styled = format::format_error_message(&message, Some(cmd), usage);
 
                 *self = Self::Formatted(styled);
             }
diff --git a/src/output/help.rs b/src/output/help.rs
index f20ccc9e21f..4405d3e68a3 100644
--- a/src/output/help.rs
+++ b/src/output/help.rs
@@ -1,1032 +1,43 @@
-// Std
-use std::borrow::Cow;
-use std::cmp;
-use std::usize;
+#![cfg_attr(not(feature = "help"), allow(unused_variables))]
 
 // Internal
-use crate::builder::PossibleValue;
-use crate::builder::Str;
+use crate::builder::Command;
 use crate::builder::StyledStr;
-use crate::builder::{Arg, Command};
-use crate::output::display_width;
-use crate::output::wrap;
 use crate::output::Usage;
-use crate::output::TAB;
-use crate::output::TAB_WIDTH;
-use crate::util::FlatSet;
 
-/// `clap` Help Writer.
-///
-/// Wraps a writer stream providing different methods to generate help for `clap` objects.
-pub(crate) struct Help<'cmd, 'writer> {
-    writer: &'writer mut StyledStr,
-    cmd: &'cmd Command,
-    usage: &'cmd Usage<'cmd>,
-    next_line_help: bool,
-    term_w: usize,
-    use_long: bool,
-}
-
-// Public Functions
-impl<'cmd, 'writer> Help<'cmd, 'writer> {
-    const DEFAULT_TEMPLATE: &'static str = "\
-{before-help}{about-with-newline}
-{usage-heading} {usage}
-
-{all-args}{after-help}\
-    ";
-
-    const DEFAULT_NO_ARGS_TEMPLATE: &'static str = "\
-{before-help}{about-with-newline}
-{usage-heading} {usage}{after-help}\
-    ";
-
-    /// Create a new `Help` instance.
-    pub(crate) fn new(
-        writer: &'writer mut StyledStr,
-        cmd: &'cmd Command,
-        usage: &'cmd Usage<'cmd>,
-        use_long: bool,
-    ) -> Self {
-        debug!("Help::new cmd={}, use_long={}", cmd.get_name(), use_long);
-        let term_w = match cmd.get_term_width() {
-            Some(0) => usize::MAX,
-            Some(w) => w,
-            None => {
-                let (current_width, _h) = dimensions();
-                let current_width = current_width.unwrap_or(100);
-                let max_width = match cmd.get_max_term_width() {
-                    None | Some(0) => usize::MAX,
-                    Some(mw) => mw,
-                };
-                cmp::min(current_width, max_width)
-            }
-        };
-        let next_line_help = cmd.is_next_line_help_set();
-
-        Help {
-            writer,
-            cmd,
-            usage,
-            next_line_help,
-            term_w,
-            use_long,
-        }
-    }
-
-    /// Writes the parser help to the wrapped stream.
-    pub(crate) fn write_help(&mut self) {
-        debug!("Help::write_help");
-
-        if let Some(h) = self.cmd.get_override_help() {
-            self.extend(h);
-        } else if let Some(tmpl) = self.cmd.get_help_template() {
-            for (style, content) in tmpl.iter() {
-                if style == None {
-                    self.write_templated_help(content);
-                } else {
-                    self.writer.stylize(style, content);
-                }
-            }
-        } else {
-            let pos = self
-                .cmd
-                .get_positionals()
-                .any(|arg| should_show_arg(self.use_long, arg));
-            let non_pos = self
-                .cmd
-                .get_non_positionals()
-                .any(|arg| should_show_arg(self.use_long, arg));
-            let subcmds = self.cmd.has_visible_subcommands();
-
-            let template = if non_pos || pos || subcmds {
-                Self::DEFAULT_TEMPLATE
-            } else {
-                Self::DEFAULT_NO_ARGS_TEMPLATE
-            };
-            self.write_templated_help(template);
-        }
-
-        // Remove any extra lines caused by book keeping
-        self.writer.trim();
-        // Ensure there is still a trailing newline
-        self.none("\n");
-    }
-}
-
-// Template
-impl<'cmd, 'writer> Help<'cmd, 'writer> {
-    /// Write help to stream for the parser in the format defined by the template.
-    ///
-    /// For details about the template language see [`Command::help_template`].
-    ///
-    /// [`Command::help_template`]: Command::help_template()
-    fn write_templated_help(&mut self, template: &str) {
-        debug!("Help::write_templated_help");
-
-        let mut parts = template.split('{');
-        if let Some(first) = parts.next() {
-            self.none(first);
-        }
-        for part in parts {
-            if let Some((tag, rest)) = part.split_once('}') {
-                match tag {
-                    "name" => {
-                        self.write_display_name();
-                    }
-                    "bin" => {
-                        self.write_bin_name();
-                    }
-                    "version" => {
-                        self.write_version();
-                    }
-                    "author" => {
-                        self.write_author(false, false);
-                    }
-                    "author-with-newline" => {
-                        self.write_author(false, true);
-                    }
-                    "author-section" => {
-                        self.write_author(true, true);
-                    }
-                    "about" => {
-                        self.write_about(false, false);
-                    }
-                    "about-with-newline" => {
-                        self.write_about(false, true);
-                    }
-                    "about-section" => {
-                        self.write_about(true, true);
-                    }
-                    "usage-heading" => {
-                        self.header("Usage:");
-                    }
-                    "usage" => {
-                        self.writer
-                            .extend(self.usage.create_usage_no_title(&[]).into_iter());
-                    }
-                    "all-args" => {
-                        self.write_all_args();
-                    }
-                    "options" => {
-                        // Include even those with a heading as we don't have a good way of
-                        // handling help_heading in the template.
-                        self.write_args(
-                            &self.cmd.get_non_positionals().collect::<Vec<_>>(),
-                            "options",
-                            option_sort_key,
-                        );
-                    }
-                    "positionals" => {
-                        self.write_args(
-                            &self.cmd.get_positionals().collect::<Vec<_>>(),
-                            "positionals",
-                            positional_sort_key,
-                        );
-                    }
-                    "subcommands" => {
-                        self.write_subcommands(self.cmd);
-                    }
-                    "tab" => {
-                        self.none(TAB);
-                    }
-                    "after-help" => {
-                        self.write_after_help();
-                    }
-                    "before-help" => {
-                        self.write_before_help();
-                    }
-                    _ => {
-                        self.none("{");
-                        self.none(tag);
-                        self.none("}");
-                    }
-                }
-                self.none(rest);
-            }
-        }
-    }
-}
-
-/// Basic template methods
-impl<'cmd, 'writer> Help<'cmd, 'writer> {
-    /// Writes binary name of a Parser Object to the wrapped stream.
-    fn write_display_name(&mut self) {
-        debug!("Help::write_display_name");
-
-        let display_name = wrap(
-            &self
-                .cmd
-                .get_display_name()
-                .unwrap_or_else(|| self.cmd.get_name())
-                .replace("{n}", "\n"),
-            self.term_w,
-        );
-        self.none(&display_name);
-    }
-
-    /// Writes binary name of a Parser Object to the wrapped stream.
-    fn write_bin_name(&mut self) {
-        debug!("Help::write_bin_name");
-
-        let bin_name = if let Some(bn) = self.cmd.get_bin_name() {
-            if bn.contains(' ') {
-                // In case we're dealing with subcommands i.e. git mv is translated to git-mv
-                bn.replace(' ', "-")
-            } else {
-                wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
-            }
-        } else {
-            wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
-        };
-        self.none(&bin_name);
-    }
-
-    fn write_version(&mut self) {
-        let version = self
-            .cmd
-            .get_version()
-            .or_else(|| self.cmd.get_long_version());
-        if let Some(output) = version {
-            self.none(wrap(output, self.term_w));
-        }
-    }
+/// Writes the parser help to the wrapped stream.
+pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_>, use_long: bool) {
+    debug!("write_help");
 
-    fn write_author(&mut self, before_new_line: bool, after_new_line: bool) {
-        if let Some(author) = self.cmd.get_author() {
-            if before_new_line {
-                self.none("\n");
-            }
-            self.none(wrap(author, self.term_w));
-            if after_new_line {
-                self.none("\n");
-            }
-        }
-    }
-
-    fn write_about(&mut self, before_new_line: bool, after_new_line: bool) {
-        let about = if self.use_long {
-            self.cmd.get_long_about().or_else(|| self.cmd.get_about())
-        } else {
-            self.cmd.get_about()
-        };
-        if let Some(output) = about {
-            if before_new_line {
-                self.none("\n");
-            }
-            let mut output = output.clone();
-            output.replace_newline();
-            output.wrap(self.term_w);
-            self.writer.extend(output.into_iter());
-            if after_new_line {
-                self.none("\n");
-            }
-        }
-    }
-
-    fn write_before_help(&mut self) {
-        debug!("Help::write_before_help");
-        let before_help = if self.use_long {
-            self.cmd
-                .get_before_long_help()
-                .or_else(|| self.cmd.get_before_help())
-        } else {
-            self.cmd.get_before_help()
-        };
-        if let Some(output) = before_help {
-            let mut output = output.clone();
-            output.replace_newline();
-            output.wrap(self.term_w);
-            self.writer.extend(output.into_iter());
-            self.none("\n\n");
-        }
-    }
-
-    fn write_after_help(&mut self) {
-        debug!("Help::write_after_help");
-        let after_help = if self.use_long {
-            self.cmd
-                .get_after_long_help()
-                .or_else(|| self.cmd.get_after_help())
-        } else {
-            self.cmd.get_after_help()
-        };
-        if let Some(output) = after_help {
-            self.none("\n\n");
-            let mut output = output.clone();
-            output.replace_newline();
-            output.wrap(self.term_w);
-            self.writer.extend(output.into_iter());
-        }
-    }
-}
-
-/// Arg handling
-impl<'cmd, 'writer> Help<'cmd, 'writer> {
-    /// Writes help for all arguments (options, flags, args, subcommands)
-    /// including titles of a Parser Object to the wrapped stream.
-    pub(crate) fn write_all_args(&mut self) {
-        debug!("Help::write_all_args");
-        let pos = self
-            .cmd
-            .get_positionals_with_no_heading()
-            .filter(|arg| should_show_arg(self.use_long, arg))
-            .collect::<Vec<_>>();
-        let non_pos = self
-            .cmd
-            .get_non_positionals_with_no_heading()
-            .filter(|arg| should_show_arg(self.use_long, arg))
-            .collect::<Vec<_>>();
-        let subcmds = self.cmd.has_visible_subcommands();
-
-        let custom_headings = self
-            .cmd
-            .get_arguments()
-            .filter_map(|arg| arg.get_help_heading())
-            .collect::<FlatSet<_>>();
-
-        let mut first = true;
-
-        if subcmds {
-            if !first {
-                self.none("\n\n");
-            }
-            first = false;
-            let default_help_heading = Str::from("Commands");
-            self.header(
-                self.cmd
-                    .get_subcommand_help_heading()
-                    .unwrap_or(&default_help_heading),
-            );
-            self.header(":\n");
-
-            self.write_subcommands(self.cmd);
-        }
-
-        if !pos.is_empty() {
-            if !first {
-                self.none("\n\n");
-            }
-            first = false;
-            // Write positional args if any
-            self.header("Arguments:\n");
-            self.write_args(&pos, "Arguments", positional_sort_key);
-        }
-
-        if !non_pos.is_empty() {
-            if !first {
-                self.none("\n\n");
-            }
-            first = false;
-            self.header("Options:\n");
-            self.write_args(&non_pos, "Options", option_sort_key);
-        }
-        if !custom_headings.is_empty() {
-            for heading in custom_headings {
-                let args = self
-                    .cmd
-                    .get_arguments()
-                    .filter(|a| {
-                        if let Some(help_heading) = a.get_help_heading() {
-                            return help_heading == heading;
-                        }
-                        false
-                    })
-                    .filter(|arg| should_show_arg(self.use_long, arg))
-                    .collect::<Vec<_>>();
-
-                if !args.is_empty() {
-                    if !first {
-                        self.none("\n\n");
+    if let Some(h) = cmd.get_override_help() {
+        writer.extend(h.iter());
+    } else {
+        #[cfg(feature = "help")]
+        {
+            use super::AutoHelp;
+            use super::HelpTemplate;
+            if let Some(tmpl) = cmd.get_help_template() {
+                for (style, content) in tmpl.iter() {
+                    if style == None {
+                        HelpTemplate::new(writer, cmd, usage, use_long)
+                            .write_templated_help(content);
+                    } else {
+                        writer.stylize(style, content);
                     }
-                    first = false;
-                    self.header(format!("{}:\n", heading));
-                    self.write_args(&args, heading, option_sort_key);
-                }
-            }
-        }
-    }
-    /// Sorts arguments by length and display order and write their help to the wrapped stream.
-    fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) {
-        debug!("Help::write_args {}", _category);
-        // The shortest an arg can legally be is 2 (i.e. '-x')
-        let mut longest = 2;
-        let mut ord_v = Vec::new();
-
-        // Determine the longest
-        for &arg in args.iter().filter(|arg| {
-            // If it's NextLineHelp we don't care to compute how long it is because it may be
-            // NextLineHelp on purpose simply *because* it's so long and would throw off all other
-            // args alignment
-            should_show_arg(self.use_long, *arg)
-        }) {
-            if arg.longest_filter() {
-                longest = longest.max(display_width(&arg.to_string()));
-                debug!(
-                    "Help::write_args: arg={:?} longest={}",
-                    arg.get_id(),
-                    longest
-                );
-            }
-
-            let key = (sort_key)(arg);
-            ord_v.push((key, arg));
-        }
-        ord_v.sort_by(|a, b| a.0.cmp(&b.0));
-
-        let next_line_help = self.will_args_wrap(args, longest);
-
-        for (i, (_, arg)) in ord_v.iter().enumerate() {
-            if i != 0 {
-                self.none("\n");
-                if next_line_help && self.use_long {
-                    self.none("\n");
                 }
-            }
-            self.write_arg(arg, next_line_help, longest);
-        }
-    }
-
-    /// Writes help for an argument to the wrapped stream.
-    fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
-        let spec_vals = &self.spec_vals(arg);
-
-        self.none(TAB);
-        self.short(arg);
-        self.long(arg);
-        self.writer.extend(arg.stylize_arg_suffix(None).into_iter());
-        self.align_to_about(arg, next_line_help, longest);
-
-        let about = if self.use_long {
-            arg.get_long_help()
-                .or_else(|| arg.get_help())
-                .unwrap_or_default()
-        } else {
-            arg.get_help()
-                .or_else(|| arg.get_long_help())
-                .unwrap_or_default()
-        };
-
-        self.help(Some(arg), about, spec_vals, next_line_help, longest);
-    }
-
-    /// Writes argument's short command to the wrapped stream.
-    fn short(&mut self, arg: &Arg) {
-        debug!("Help::short");
-
-        if let Some(s) = arg.get_short() {
-            self.literal(format!("-{}", s));
-        } else if arg.get_long().is_some() {
-            self.none("    ");
-        }
-    }
-
-    /// Writes argument's long command to the wrapped stream.
-    fn long(&mut self, arg: &Arg) {
-        debug!("Help::long");
-        if let Some(long) = arg.get_long() {
-            if arg.short.is_some() {
-                self.none(", ");
-            }
-            self.literal(format!("--{}", long));
-        }
-    }
-
-    /// Write alignment padding between arg's switches/values and its about message.
-    fn align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
-        debug!(
-            "Help::align_to_about: arg={}, next_line_help={}, longest={}",
-            arg.get_id(),
-            next_line_help,
-            longest
-        );
-        if self.use_long || next_line_help {
-            // long help prints messages on the next line so it doesn't need to align text
-            debug!("Help::align_to_about: printing long help so skip alignment");
-        } else if !arg.is_positional() {
-            let self_len = display_width(&arg.to_string());
-            // Since we're writing spaces from the tab point we first need to know if we
-            // had a long and short, or just short
-            let padding = if arg.long.is_some() {
-                // Only account 4 after the val
-                TAB_WIDTH
             } else {
-                // Only account for ', --' + 4 after the val
-                TAB_WIDTH + 4
-            };
-            let spcs = longest + padding - self_len;
-            debug!(
-                "Help::align_to_about: positional=false arg_len={}, spaces={}",
-                self_len, spcs
-            );
-
-            self.spaces(spcs);
-        } else {
-            let self_len = display_width(&arg.to_string());
-            let padding = TAB_WIDTH;
-            let spcs = longest + padding - self_len;
-            debug!(
-                "Help::align_to_about: positional=true arg_len={}, spaces={}",
-                self_len, spcs
-            );
-
-            self.spaces(spcs);
-        }
-    }
-
-    /// Writes argument's help to the wrapped stream.
-    fn help(
-        &mut self,
-        arg: Option<&Arg>,
-        about: &StyledStr,
-        spec_vals: &str,
-        next_line_help: bool,
-        longest: usize,
-    ) {
-        debug!("Help::help");
-
-        // Is help on next line, if so then indent
-        if next_line_help {
-            debug!("Help::help: Next Line...{:?}", next_line_help);
-            self.none("\n");
-            self.none(TAB);
-            self.none(NEXT_LINE_INDENT);
-        }
-
-        let spaces = if next_line_help {
-            TAB.len() + NEXT_LINE_INDENT.len()
-        } else if let Some(true) = arg.map(|a| a.is_positional()) {
-            longest + TAB_WIDTH * 2
-        } else {
-            longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4
-        };
-        let trailing_indent = spaces; // Don't indent any further than the first line is indented
-        let trailing_indent = self.get_spaces(trailing_indent);
-
-        let mut help = about.clone();
-        help.replace_newline();
-        if !spec_vals.is_empty() {
-            if !help.is_empty() {
-                let sep = if self.use_long && arg.is_some() {
-                    "\n\n"
-                } else {
-                    " "
-                };
-                help.none(sep);
+                AutoHelp::new(writer, cmd, usage, use_long).write_help();
             }
-            help.none(spec_vals);
         }
-        let avail_chars = self.term_w.saturating_sub(spaces);
-        debug!(
-            "Help::help: help_width={}, spaces={}, avail={}",
-            spaces,
-            help.display_width(),
-            avail_chars
-        );
-        help.wrap(avail_chars);
-        help.indent("", &trailing_indent);
-        let help_is_empty = help.is_empty();
-        self.writer.extend(help.into_iter());
-        if let Some(arg) = arg {
-            const DASH_SPACE: usize = "- ".len();
-            const COLON_SPACE: usize = ": ".len();
-            let possible_vals = arg.get_possible_values();
-            if self.use_long
-                && !arg.is_hide_possible_values_set()
-                && possible_vals.iter().any(PossibleValue::should_show_help)
-            {
-                debug!("Help::help: Found possible vals...{:?}", possible_vals);
-                if !help_is_empty {
-                    self.none("\n\n");
-                    self.spaces(spaces);
-                }
-                self.none("Possible values:");
-                let longest = possible_vals
-                    .iter()
-                    .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name)))
-                    .max()
-                    .expect("Only called with possible value");
-                let help_longest = possible_vals
-                    .iter()
-                    .filter_map(|f| f.get_visible_help().map(|h| h.display_width()))
-                    .max()
-                    .expect("Only called with possible value with help");
-                // should new line
-                let taken = longest + spaces + DASH_SPACE;
-
-                let possible_value_new_line =
-                    self.term_w >= taken && self.term_w < taken + COLON_SPACE + help_longest;
 
-                let spaces = spaces + TAB_WIDTH - DASH_SPACE;
-                let trailing_indent = if possible_value_new_line {
-                    spaces + DASH_SPACE
-                } else {
-                    spaces + longest + DASH_SPACE + COLON_SPACE
-                };
-                let trailing_indent = self.get_spaces(trailing_indent);
-
-                for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) {
-                    self.none("\n");
-                    self.spaces(spaces);
-                    self.none("- ");
-                    self.literal(pv.get_name());
-                    if let Some(help) = pv.get_help() {
-                        debug!("Help::help: Possible Value help");
-
-                        if possible_value_new_line {
-                            self.none(":\n");
-                            self.spaces(trailing_indent.len());
-                        } else {
-                            self.none(": ");
-                            // To align help messages
-                            self.spaces(longest - display_width(pv.get_name()));
-                        }
-
-                        let avail_chars = if self.term_w > trailing_indent.len() {
-                            self.term_w - trailing_indent.len()
-                        } else {
-                            usize::MAX
-                        };
-
-                        let mut help = help.clone();
-                        help.replace_newline();
-                        help.wrap(avail_chars);
-                        help.indent("", &trailing_indent);
-                        self.writer.extend(help.into_iter());
-                    }
-                }
-            }
-        }
-    }
-
-    /// Will use next line help on writing args.
-    fn will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool {
-        args.iter()
-            .filter(|arg| should_show_arg(self.use_long, *arg))
-            .any(|arg| {
-                let spec_vals = &self.spec_vals(arg);
-                self.arg_next_line_help(arg, spec_vals, longest)
-            })
-    }
-
-    fn arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool {
-        if self.next_line_help || arg.is_next_line_help_set() || self.use_long {
-            // setting_next_line
-            true
-        } else {
-            // force_next_line
-            let h = arg.get_help().unwrap_or_default();
-            let h_w = h.display_width() + display_width(spec_vals);
-            let taken = longest + 12;
-            self.term_w >= taken
-                && (taken as f32 / self.term_w as f32) > 0.40
-                && h_w > (self.term_w - taken)
-        }
-    }
-
-    fn spec_vals(&self, a: &Arg) -> String {
-        debug!("Help::spec_vals: a={}", a);
-        let mut spec_vals = Vec::new();
-        #[cfg(feature = "env")]
-        if let Some(ref env) = a.env {
-            if !a.is_hide_env_set() {
-                debug!(
-                    "Help::spec_vals: Found environment variable...[{:?}:{:?}]",
-                    env.0, env.1
-                );
-                let env_val = if !a.is_hide_env_values_set() {
-                    format!(
-                        "={}",
-                        env.1
-                            .as_ref()
-                            .map(|s| s.to_string_lossy())
-                            .unwrap_or_default()
-                    )
-                } else {
-                    Default::default()
-                };
-                let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
-                spec_vals.push(env_info);
-            }
-        }
-        if a.is_takes_value_set() && !a.is_hide_default_value_set() && !a.default_vals.is_empty() {
-            debug!(
-                "Help::spec_vals: Found default value...[{:?}]",
-                a.default_vals
-            );
-
-            let pvs = a
-                .default_vals
-                .iter()
-                .map(|pvs| pvs.to_string_lossy())
-                .map(|pvs| {
-                    if pvs.contains(char::is_whitespace) {
-                        Cow::from(format!("{:?}", pvs))
-                    } else {
-                        pvs
-                    }
-                })
-                .collect::<Vec<_>>()
-                .join(" ");
-
-            spec_vals.push(format!("[default: {}]", pvs));
-        }
-
-        let als = a
-            .aliases
-            .iter()
-            .filter(|&als| als.1) // visible
-            .map(|als| als.0.as_str()) // name
-            .collect::<Vec<_>>()
-            .join(", ");
-        if !als.is_empty() {
-            debug!("Help::spec_vals: Found aliases...{:?}", a.aliases);
-            spec_vals.push(format!("[aliases: {}]", als));
-        }
-
-        let als = a
-            .short_aliases
-            .iter()
-            .filter(|&als| als.1) // visible
-            .map(|&als| als.0.to_string()) // name
-            .collect::<Vec<_>>()
-            .join(", ");
-        if !als.is_empty() {
-            debug!(
-                "Help::spec_vals: Found short aliases...{:?}",
-                a.short_aliases
-            );
-            spec_vals.push(format!("[short aliases: {}]", als));
-        }
-
-        let possible_vals = a.get_possible_values();
-        if !(a.is_hide_possible_values_set()
-            || possible_vals.is_empty()
-            || self.use_long && possible_vals.iter().any(PossibleValue::should_show_help))
+        #[cfg(not(feature = "help"))]
         {
-            debug!("Help::spec_vals: Found possible vals...{:?}", possible_vals);
-
-            let pvs = possible_vals
-                .iter()
-                .filter_map(PossibleValue::get_visible_quoted_name)
-                .collect::<Vec<_>>()
-                .join(", ");
-
-            spec_vals.push(format!("[possible values: {}]", pvs));
+            debug!("write_help: no help, `Command::override_help` and `help` is missing");
         }
-        let connector = if self.use_long { "\n" } else { " " };
-        spec_vals.join(connector)
-    }
-
-    fn extend(&mut self, msg: &StyledStr) {
-        self.writer.extend(msg.iter());
-    }
-
-    fn header<T: Into<String>>(&mut self, msg: T) {
-        self.writer.header(msg);
-    }
-
-    fn literal<T: Into<String>>(&mut self, msg: T) {
-        self.writer.literal(msg);
     }
 
-    fn none<T: Into<String>>(&mut self, msg: T) {
-        self.writer.none(msg);
-    }
-
-    fn get_spaces(&self, n: usize) -> String {
-        " ".repeat(n)
-    }
-
-    fn spaces(&mut self, n: usize) {
-        self.none(self.get_spaces(n));
-    }
-}
-
-/// Subcommand handling
-impl<'cmd, 'writer> Help<'cmd, 'writer> {
-    /// Writes help for subcommands of a Parser Object to the wrapped stream.
-    fn write_subcommands(&mut self, cmd: &Command) {
-        debug!("Help::write_subcommands");
-        // The shortest an arg can legally be is 2 (i.e. '-x')
-        let mut longest = 2;
-        let mut ord_v = Vec::new();
-        for subcommand in cmd
-            .get_subcommands()
-            .filter(|subcommand| should_show_subcommand(subcommand))
-        {
-            let mut styled = StyledStr::new();
-            styled.literal(subcommand.get_name());
-            if let Some(short) = subcommand.get_short_flag() {
-                styled.none(", ");
-                styled.literal(format!("-{}", short));
-            }
-            if let Some(long) = subcommand.get_long_flag() {
-                styled.none(", ");
-                styled.literal(format!("--{}", long));
-            }
-            longest = longest.max(styled.display_width());
-            ord_v.push((subcommand.get_display_order(), styled, subcommand));
-        }
-        ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
-
-        debug!("Help::write_subcommands longest = {}", longest);
-
-        let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
-
-        let mut first = true;
-        for (_, sc_str, sc) in ord_v {
-            if first {
-                first = false;
-            } else {
-                self.none("\n");
-            }
-            self.write_subcommand(sc_str, sc, next_line_help, longest);
-        }
-    }
-
-    /// Will use next line help on writing subcommands.
-    fn will_subcommands_wrap<'a>(
-        &self,
-        subcommands: impl IntoIterator<Item = &'a Command>,
-        longest: usize,
-    ) -> bool {
-        subcommands
-            .into_iter()
-            .filter(|&subcommand| should_show_subcommand(subcommand))
-            .any(|subcommand| {
-                let spec_vals = &self.sc_spec_vals(subcommand);
-                self.subcommand_next_line_help(subcommand, spec_vals, longest)
-            })
-    }
-
-    fn write_subcommand(
-        &mut self,
-        sc_str: StyledStr,
-        cmd: &Command,
-        next_line_help: bool,
-        longest: usize,
-    ) {
-        debug!("Help::write_subcommand");
-
-        let spec_vals = &self.sc_spec_vals(cmd);
-
-        let about = cmd
-            .get_about()
-            .or_else(|| cmd.get_long_about())
-            .unwrap_or_default();
-
-        self.subcmd(sc_str, next_line_help, longest);
-        self.help(None, about, spec_vals, next_line_help, longest)
-    }
-
-    fn sc_spec_vals(&self, a: &Command) -> String {
-        debug!("Help::sc_spec_vals: a={}", a.get_name());
-        let mut spec_vals = vec![];
-
-        let mut short_als = a
-            .get_visible_short_flag_aliases()
-            .map(|a| format!("-{}", a))
-            .collect::<Vec<_>>();
-        let als = a.get_visible_aliases().map(|s| s.to_string());
-        short_als.extend(als);
-        let all_als = short_als.join(", ");
-        if !all_als.is_empty() {
-            debug!(
-                "Help::spec_vals: Found aliases...{:?}",
-                a.get_all_aliases().collect::<Vec<_>>()
-            );
-            debug!(
-                "Help::spec_vals: Found short flag aliases...{:?}",
-                a.get_all_short_flag_aliases().collect::<Vec<_>>()
-            );
-            spec_vals.push(format!("[aliases: {}]", all_als));
-        }
-
-        spec_vals.join(" ")
-    }
-
-    fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool {
-        if self.next_line_help | self.use_long {
-            // setting_next_line
-            true
-        } else {
-            // force_next_line
-            let h = cmd.get_about().unwrap_or_default();
-            let h_w = h.display_width() + display_width(spec_vals);
-            let taken = longest + 12;
-            self.term_w >= taken
-                && (taken as f32 / self.term_w as f32) > 0.40
-                && h_w > (self.term_w - taken)
-        }
-    }
-
-    /// Writes subcommand to the wrapped stream.
-    fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
-        let width = sc_str.display_width();
-
-        self.none(TAB);
-        self.writer.extend(sc_str.into_iter());
-        if !next_line_help {
-            self.spaces(width.max(longest + TAB_WIDTH) - width);
-        }
-    }
-}
-
-const NEXT_LINE_INDENT: &str = "        ";
-
-type ArgSortKey = fn(arg: &Arg) -> (usize, String);
-
-fn positional_sort_key(arg: &Arg) -> (usize, String) {
-    (arg.get_index().unwrap_or(0), String::new())
-}
-
-fn option_sort_key(arg: &Arg) -> (usize, String) {
-    // Formatting key like this to ensure that:
-    // 1. Argument has long flags are printed just after short flags.
-    // 2. For two args both have short flags like `-c` and `-C`, the
-    //    `-C` arg is printed just after the `-c` arg
-    // 3. For args without short or long flag, print them at last(sorted
-    //    by arg name).
-    // Example order: -a, -b, -B, -s, --select-file, --select-folder, -x
-
-    let key = if let Some(x) = arg.get_short() {
-        let mut s = x.to_ascii_lowercase().to_string();
-        s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
-        s
-    } else if let Some(x) = arg.get_long() {
-        x.to_string()
-    } else {
-        let mut s = '{'.to_string();
-        s.push_str(arg.id.as_str());
-        s
-    };
-    (arg.get_display_order(), key)
-}
-
-pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) {
-    #[cfg(not(feature = "wrap_help"))]
-    return (None, None);
-
-    #[cfg(feature = "wrap_help")]
-    terminal_size::terminal_size()
-        .map(|(w, h)| (Some(w.0.into()), Some(h.0.into())))
-        .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES")))
-}
-
-#[cfg(feature = "wrap_help")]
-fn parse_env(var: &str) -> Option<usize> {
-    std::env::var_os(var)?.to_str()?.parse::<usize>().ok()
-}
-
-fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
-    debug!(
-        "should_show_arg: use_long={:?}, arg={}",
-        use_long,
-        arg.get_id()
-    );
-    if arg.is_hide_set() {
-        return false;
-    }
-    (!arg.is_hide_long_help_set() && use_long)
-        || (!arg.is_hide_short_help_set() && !use_long)
-        || arg.is_next_line_help_set()
-}
-
-fn should_show_subcommand(subcommand: &Command) -> bool {
-    !subcommand.is_hide_set()
-}
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    #[test]
-    fn wrap_help_last_word() {
-        let help = String::from("foo bar baz");
-        assert_eq!(wrap(&help, 5), "foo\nbar\nbaz");
-    }
-
-    #[test]
-    #[cfg(feature = "unicode")]
-    fn display_width_handles_non_ascii() {
-        // Popular Danish tongue-twister, the name of a fruit dessert.
-        let text = "rødgrød med fløde";
-        assert_eq!(display_width(text), 17);
-        // Note that the string width is smaller than the string
-        // length. This is due to the precomposed non-ASCII letters:
-        assert_eq!(text.len(), 20);
-    }
-
-    #[test]
-    #[cfg(feature = "unicode")]
-    fn display_width_handles_emojis() {
-        let text = "😂";
-        // There is a single `char`...
-        assert_eq!(text.chars().count(), 1);
-        // but it is double-width:
-        assert_eq!(display_width(text), 2);
-        // This is much less than the byte length:
-        assert_eq!(text.len(), 4);
-    }
+    // Remove any extra lines caused by book keeping
+    writer.trim();
+    // Ensure there is still a trailing newline
+    writer.none("\n");
 }
diff --git a/src/output/help_template.rs b/src/output/help_template.rs
new file mode 100644
index 00000000000..da16f903fdd
--- /dev/null
+++ b/src/output/help_template.rs
@@ -0,0 +1,1053 @@
+// Std
+use std::borrow::Cow;
+use std::cmp;
+use std::usize;
+
+// Internal
+use crate::builder::PossibleValue;
+use crate::builder::Str;
+use crate::builder::StyledStr;
+use crate::builder::{Arg, Command};
+use crate::output::display_width;
+use crate::output::wrap;
+use crate::output::Usage;
+use crate::output::TAB;
+use crate::output::TAB_WIDTH;
+use crate::util::FlatSet;
+
+/// `clap` auto-generated help writer
+pub(crate) struct AutoHelp<'cmd, 'writer> {
+    template: HelpTemplate<'cmd, 'writer>,
+}
+
+// Public Functions
+impl<'cmd, 'writer> AutoHelp<'cmd, 'writer> {
+    /// Create a new `HelpTemplate` instance.
+    pub(crate) fn new(
+        writer: &'writer mut StyledStr,
+        cmd: &'cmd Command,
+        usage: &'cmd Usage<'cmd>,
+        use_long: bool,
+    ) -> Self {
+        Self {
+            template: HelpTemplate::new(writer, cmd, usage, use_long),
+        }
+    }
+
+    pub(crate) fn write_help(&mut self) {
+        let pos = self
+            .template
+            .cmd
+            .get_positionals()
+            .any(|arg| should_show_arg(self.template.use_long, arg));
+        let non_pos = self
+            .template
+            .cmd
+            .get_non_positionals()
+            .any(|arg| should_show_arg(self.template.use_long, arg));
+        let subcmds = self.template.cmd.has_visible_subcommands();
+
+        let template = if non_pos || pos || subcmds {
+            DEFAULT_TEMPLATE
+        } else {
+            DEFAULT_NO_ARGS_TEMPLATE
+        };
+        self.template.write_templated_help(template);
+    }
+}
+
+const DEFAULT_TEMPLATE: &str = "\
+{before-help}{about-with-newline}
+{usage-heading} {usage}
+
+{all-args}{after-help}\
+    ";
+
+const DEFAULT_NO_ARGS_TEMPLATE: &str = "\
+{before-help}{about-with-newline}
+{usage-heading} {usage}{after-help}\
+    ";
+
+/// `clap` HelpTemplate Writer.
+///
+/// Wraps a writer stream providing different methods to generate help for `clap` objects.
+pub(crate) struct HelpTemplate<'cmd, 'writer> {
+    writer: &'writer mut StyledStr,
+    cmd: &'cmd Command,
+    usage: &'cmd Usage<'cmd>,
+    next_line_help: bool,
+    term_w: usize,
+    use_long: bool,
+}
+
+// Public Functions
+impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
+    /// Create a new `HelpTemplate` instance.
+    pub(crate) fn new(
+        writer: &'writer mut StyledStr,
+        cmd: &'cmd Command,
+        usage: &'cmd Usage<'cmd>,
+        use_long: bool,
+    ) -> Self {
+        debug!(
+            "HelpTemplate::new cmd={}, use_long={}",
+            cmd.get_name(),
+            use_long
+        );
+        let term_w = match cmd.get_term_width() {
+            Some(0) => usize::MAX,
+            Some(w) => w,
+            None => {
+                let (current_width, _h) = dimensions();
+                let current_width = current_width.unwrap_or(100);
+                let max_width = match cmd.get_max_term_width() {
+                    None | Some(0) => usize::MAX,
+                    Some(mw) => mw,
+                };
+                cmp::min(current_width, max_width)
+            }
+        };
+        let next_line_help = cmd.is_next_line_help_set();
+
+        HelpTemplate {
+            writer,
+            cmd,
+            usage,
+            next_line_help,
+            term_w,
+            use_long,
+        }
+    }
+
+    /// Write help to stream for the parser in the format defined by the template.
+    ///
+    /// For details about the template language see [`Command::help_template`].
+    ///
+    /// [`Command::help_template`]: Command::help_template()
+    pub(crate) fn write_templated_help(&mut self, template: &str) {
+        debug!("HelpTemplate::write_templated_help");
+
+        let mut parts = template.split('{');
+        if let Some(first) = parts.next() {
+            self.none(first);
+        }
+        for part in parts {
+            if let Some((tag, rest)) = part.split_once('}') {
+                match tag {
+                    "name" => {
+                        self.write_display_name();
+                    }
+                    "bin" => {
+                        self.write_bin_name();
+                    }
+                    "version" => {
+                        self.write_version();
+                    }
+                    "author" => {
+                        self.write_author(false, false);
+                    }
+                    "author-with-newline" => {
+                        self.write_author(false, true);
+                    }
+                    "author-section" => {
+                        self.write_author(true, true);
+                    }
+                    "about" => {
+                        self.write_about(false, false);
+                    }
+                    "about-with-newline" => {
+                        self.write_about(false, true);
+                    }
+                    "about-section" => {
+                        self.write_about(true, true);
+                    }
+                    "usage-heading" => {
+                        self.header("Usage:");
+                    }
+                    "usage" => {
+                        self.writer.extend(
+                            self.usage
+                                .create_usage_no_title(&[])
+                                .unwrap_or_default()
+                                .into_iter(),
+                        );
+                    }
+                    "all-args" => {
+                        self.write_all_args();
+                    }
+                    "options" => {
+                        // Include even those with a heading as we don't have a good way of
+                        // handling help_heading in the template.
+                        self.write_args(
+                            &self.cmd.get_non_positionals().collect::<Vec<_>>(),
+                            "options",
+                            option_sort_key,
+                        );
+                    }
+                    "positionals" => {
+                        self.write_args(
+                            &self.cmd.get_positionals().collect::<Vec<_>>(),
+                            "positionals",
+                            positional_sort_key,
+                        );
+                    }
+                    "subcommands" => {
+                        self.write_subcommands(self.cmd);
+                    }
+                    "tab" => {
+                        self.none(TAB);
+                    }
+                    "after-help" => {
+                        self.write_after_help();
+                    }
+                    "before-help" => {
+                        self.write_before_help();
+                    }
+                    _ => {
+                        self.none("{");
+                        self.none(tag);
+                        self.none("}");
+                    }
+                }
+                self.none(rest);
+            }
+        }
+    }
+}
+
+/// Basic template methods
+impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
+    /// Writes binary name of a Parser Object to the wrapped stream.
+    fn write_display_name(&mut self) {
+        debug!("HelpTemplate::write_display_name");
+
+        let display_name = wrap(
+            &self
+                .cmd
+                .get_display_name()
+                .unwrap_or_else(|| self.cmd.get_name())
+                .replace("{n}", "\n"),
+            self.term_w,
+        );
+        self.none(&display_name);
+    }
+
+    /// Writes binary name of a Parser Object to the wrapped stream.
+    fn write_bin_name(&mut self) {
+        debug!("HelpTemplate::write_bin_name");
+
+        let bin_name = if let Some(bn) = self.cmd.get_bin_name() {
+            if bn.contains(' ') {
+                // In case we're dealing with subcommands i.e. git mv is translated to git-mv
+                bn.replace(' ', "-")
+            } else {
+                wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
+            }
+        } else {
+            wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w)
+        };
+        self.none(&bin_name);
+    }
+
+    fn write_version(&mut self) {
+        let version = self
+            .cmd
+            .get_version()
+            .or_else(|| self.cmd.get_long_version());
+        if let Some(output) = version {
+            self.none(wrap(output, self.term_w));
+        }
+    }
+
+    fn write_author(&mut self, before_new_line: bool, after_new_line: bool) {
+        if let Some(author) = self.cmd.get_author() {
+            if before_new_line {
+                self.none("\n");
+            }
+            self.none(wrap(author, self.term_w));
+            if after_new_line {
+                self.none("\n");
+            }
+        }
+    }
+
+    fn write_about(&mut self, before_new_line: bool, after_new_line: bool) {
+        let about = if self.use_long {
+            self.cmd.get_long_about().or_else(|| self.cmd.get_about())
+        } else {
+            self.cmd.get_about()
+        };
+        if let Some(output) = about {
+            if before_new_line {
+                self.none("\n");
+            }
+            let mut output = output.clone();
+            replace_newline_var(&mut output);
+            output.wrap(self.term_w);
+            self.writer.extend(output.into_iter());
+            if after_new_line {
+                self.none("\n");
+            }
+        }
+    }
+
+    fn write_before_help(&mut self) {
+        debug!("HelpTemplate::write_before_help");
+        let before_help = if self.use_long {
+            self.cmd
+                .get_before_long_help()
+                .or_else(|| self.cmd.get_before_help())
+        } else {
+            self.cmd.get_before_help()
+        };
+        if let Some(output) = before_help {
+            let mut output = output.clone();
+            replace_newline_var(&mut output);
+            output.wrap(self.term_w);
+            self.writer.extend(output.into_iter());
+            self.none("\n\n");
+        }
+    }
+
+    fn write_after_help(&mut self) {
+        debug!("HelpTemplate::write_after_help");
+        let after_help = if self.use_long {
+            self.cmd
+                .get_after_long_help()
+                .or_else(|| self.cmd.get_after_help())
+        } else {
+            self.cmd.get_after_help()
+        };
+        if let Some(output) = after_help {
+            self.none("\n\n");
+            let mut output = output.clone();
+            replace_newline_var(&mut output);
+            output.wrap(self.term_w);
+            self.writer.extend(output.into_iter());
+        }
+    }
+}
+
+/// Arg handling
+impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
+    /// Writes help for all arguments (options, flags, args, subcommands)
+    /// including titles of a Parser Object to the wrapped stream.
+    pub(crate) fn write_all_args(&mut self) {
+        debug!("HelpTemplate::write_all_args");
+        let pos = self
+            .cmd
+            .get_positionals()
+            .filter(|a| a.get_help_heading().is_none())
+            .filter(|arg| should_show_arg(self.use_long, arg))
+            .collect::<Vec<_>>();
+        let non_pos = self
+            .cmd
+            .get_non_positionals()
+            .filter(|a| a.get_help_heading().is_none())
+            .filter(|arg| should_show_arg(self.use_long, arg))
+            .collect::<Vec<_>>();
+        let subcmds = self.cmd.has_visible_subcommands();
+
+        let custom_headings = self
+            .cmd
+            .get_arguments()
+            .filter_map(|arg| arg.get_help_heading())
+            .collect::<FlatSet<_>>();
+
+        let mut first = true;
+
+        if subcmds {
+            if !first {
+                self.none("\n\n");
+            }
+            first = false;
+            let default_help_heading = Str::from("Commands");
+            self.header(
+                self.cmd
+                    .get_subcommand_help_heading()
+                    .unwrap_or(&default_help_heading),
+            );
+            self.header(":\n");
+
+            self.write_subcommands(self.cmd);
+        }
+
+        if !pos.is_empty() {
+            if !first {
+                self.none("\n\n");
+            }
+            first = false;
+            // Write positional args if any
+            self.header("Arguments:\n");
+            self.write_args(&pos, "Arguments", positional_sort_key);
+        }
+
+        if !non_pos.is_empty() {
+            if !first {
+                self.none("\n\n");
+            }
+            first = false;
+            self.header("Options:\n");
+            self.write_args(&non_pos, "Options", option_sort_key);
+        }
+        if !custom_headings.is_empty() {
+            for heading in custom_headings {
+                let args = self
+                    .cmd
+                    .get_arguments()
+                    .filter(|a| {
+                        if let Some(help_heading) = a.get_help_heading() {
+                            return help_heading == heading;
+                        }
+                        false
+                    })
+                    .filter(|arg| should_show_arg(self.use_long, arg))
+                    .collect::<Vec<_>>();
+
+                if !args.is_empty() {
+                    if !first {
+                        self.none("\n\n");
+                    }
+                    first = false;
+                    self.header(format!("{}:\n", heading));
+                    self.write_args(&args, heading, option_sort_key);
+                }
+            }
+        }
+    }
+    /// Sorts arguments by length and display order and write their help to the wrapped stream.
+    fn write_args(&mut self, args: &[&Arg], _category: &str, sort_key: ArgSortKey) {
+        debug!("HelpTemplate::write_args {}", _category);
+        // The shortest an arg can legally be is 2 (i.e. '-x')
+        let mut longest = 2;
+        let mut ord_v = Vec::new();
+
+        // Determine the longest
+        for &arg in args.iter().filter(|arg| {
+            // If it's NextLineHelp we don't care to compute how long it is because it may be
+            // NextLineHelp on purpose simply *because* it's so long and would throw off all other
+            // args alignment
+            should_show_arg(self.use_long, *arg)
+        }) {
+            if longest_filter(arg) {
+                longest = longest.max(display_width(&arg.to_string()));
+                debug!(
+                    "HelpTemplate::write_args: arg={:?} longest={}",
+                    arg.get_id(),
+                    longest
+                );
+            }
+
+            let key = (sort_key)(arg);
+            ord_v.push((key, arg));
+        }
+        ord_v.sort_by(|a, b| a.0.cmp(&b.0));
+
+        let next_line_help = self.will_args_wrap(args, longest);
+
+        for (i, (_, arg)) in ord_v.iter().enumerate() {
+            if i != 0 {
+                self.none("\n");
+                if next_line_help && self.use_long {
+                    self.none("\n");
+                }
+            }
+            self.write_arg(arg, next_line_help, longest);
+        }
+    }
+
+    /// Writes help for an argument to the wrapped stream.
+    fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
+        let spec_vals = &self.spec_vals(arg);
+
+        self.none(TAB);
+        self.short(arg);
+        self.long(arg);
+        self.writer.extend(arg.stylize_arg_suffix(None).into_iter());
+        self.align_to_about(arg, next_line_help, longest);
+
+        let about = if self.use_long {
+            arg.get_long_help()
+                .or_else(|| arg.get_help())
+                .unwrap_or_default()
+        } else {
+            arg.get_help()
+                .or_else(|| arg.get_long_help())
+                .unwrap_or_default()
+        };
+
+        self.help(Some(arg), about, spec_vals, next_line_help, longest);
+    }
+
+    /// Writes argument's short command to the wrapped stream.
+    fn short(&mut self, arg: &Arg) {
+        debug!("HelpTemplate::short");
+
+        if let Some(s) = arg.get_short() {
+            self.literal(format!("-{}", s));
+        } else if arg.get_long().is_some() {
+            self.none("    ");
+        }
+    }
+
+    /// Writes argument's long command to the wrapped stream.
+    fn long(&mut self, arg: &Arg) {
+        debug!("HelpTemplate::long");
+        if let Some(long) = arg.get_long() {
+            if arg.short.is_some() {
+                self.none(", ");
+            }
+            self.literal(format!("--{}", long));
+        }
+    }
+
+    /// Write alignment padding between arg's switches/values and its about message.
+    fn align_to_about(&mut self, arg: &Arg, next_line_help: bool, longest: usize) {
+        debug!(
+            "HelpTemplate::align_to_about: arg={}, next_line_help={}, longest={}",
+            arg.get_id(),
+            next_line_help,
+            longest
+        );
+        if self.use_long || next_line_help {
+            // long help prints messages on the next line so it doesn't need to align text
+            debug!("HelpTemplate::align_to_about: printing long help so skip alignment");
+        } else if !arg.is_positional() {
+            let self_len = display_width(&arg.to_string());
+            // Since we're writing spaces from the tab point we first need to know if we
+            // had a long and short, or just short
+            let padding = if arg.long.is_some() {
+                // Only account 4 after the val
+                TAB_WIDTH
+            } else {
+                // Only account for ', --' + 4 after the val
+                TAB_WIDTH + 4
+            };
+            let spcs = longest + padding - self_len;
+            debug!(
+                "HelpTemplate::align_to_about: positional=false arg_len={}, spaces={}",
+                self_len, spcs
+            );
+
+            self.spaces(spcs);
+        } else {
+            let self_len = display_width(&arg.to_string());
+            let padding = TAB_WIDTH;
+            let spcs = longest + padding - self_len;
+            debug!(
+                "HelpTemplate::align_to_about: positional=true arg_len={}, spaces={}",
+                self_len, spcs
+            );
+
+            self.spaces(spcs);
+        }
+    }
+
+    /// Writes argument's help to the wrapped stream.
+    fn help(
+        &mut self,
+        arg: Option<&Arg>,
+        about: &StyledStr,
+        spec_vals: &str,
+        next_line_help: bool,
+        longest: usize,
+    ) {
+        debug!("HelpTemplate::help");
+
+        // Is help on next line, if so then indent
+        if next_line_help {
+            debug!("HelpTemplate::help: Next Line...{:?}", next_line_help);
+            self.none("\n");
+            self.none(TAB);
+            self.none(NEXT_LINE_INDENT);
+        }
+
+        let spaces = if next_line_help {
+            TAB.len() + NEXT_LINE_INDENT.len()
+        } else if let Some(true) = arg.map(|a| a.is_positional()) {
+            longest + TAB_WIDTH * 2
+        } else {
+            longest + TAB_WIDTH * 2 + 4 // See `fn short` for the 4
+        };
+        let trailing_indent = spaces; // Don't indent any further than the first line is indented
+        let trailing_indent = self.get_spaces(trailing_indent);
+
+        let mut help = about.clone();
+        replace_newline_var(&mut help);
+        if !spec_vals.is_empty() {
+            if !help.is_empty() {
+                let sep = if self.use_long && arg.is_some() {
+                    "\n\n"
+                } else {
+                    " "
+                };
+                help.none(sep);
+            }
+            help.none(spec_vals);
+        }
+        let avail_chars = self.term_w.saturating_sub(spaces);
+        debug!(
+            "HelpTemplate::help: help_width={}, spaces={}, avail={}",
+            spaces,
+            help.display_width(),
+            avail_chars
+        );
+        help.wrap(avail_chars);
+        help.indent("", &trailing_indent);
+        let help_is_empty = help.is_empty();
+        self.writer.extend(help.into_iter());
+        if let Some(arg) = arg {
+            const DASH_SPACE: usize = "- ".len();
+            const COLON_SPACE: usize = ": ".len();
+            let possible_vals = arg.get_possible_values();
+            if self.use_long
+                && !arg.is_hide_possible_values_set()
+                && possible_vals.iter().any(PossibleValue::should_show_help)
+            {
+                debug!(
+                    "HelpTemplate::help: Found possible vals...{:?}",
+                    possible_vals
+                );
+                if !help_is_empty {
+                    self.none("\n\n");
+                    self.spaces(spaces);
+                }
+                self.none("Possible values:");
+                let longest = possible_vals
+                    .iter()
+                    .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name)))
+                    .max()
+                    .expect("Only called with possible value");
+                let help_longest = possible_vals
+                    .iter()
+                    .filter_map(|f| f.get_visible_help().map(|h| h.display_width()))
+                    .max()
+                    .expect("Only called with possible value with help");
+                // should new line
+                let taken = longest + spaces + DASH_SPACE;
+
+                let possible_value_new_line =
+                    self.term_w >= taken && self.term_w < taken + COLON_SPACE + help_longest;
+
+                let spaces = spaces + TAB_WIDTH - DASH_SPACE;
+                let trailing_indent = if possible_value_new_line {
+                    spaces + DASH_SPACE
+                } else {
+                    spaces + longest + DASH_SPACE + COLON_SPACE
+                };
+                let trailing_indent = self.get_spaces(trailing_indent);
+
+                for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) {
+                    self.none("\n");
+                    self.spaces(spaces);
+                    self.none("- ");
+                    self.literal(pv.get_name());
+                    if let Some(help) = pv.get_help() {
+                        debug!("HelpTemplate::help: Possible Value help");
+
+                        if possible_value_new_line {
+                            self.none(":\n");
+                            self.spaces(trailing_indent.len());
+                        } else {
+                            self.none(": ");
+                            // To align help messages
+                            self.spaces(longest - display_width(pv.get_name()));
+                        }
+
+                        let avail_chars = if self.term_w > trailing_indent.len() {
+                            self.term_w - trailing_indent.len()
+                        } else {
+                            usize::MAX
+                        };
+
+                        let mut help = help.clone();
+                        replace_newline_var(&mut help);
+                        help.wrap(avail_chars);
+                        help.indent("", &trailing_indent);
+                        self.writer.extend(help.into_iter());
+                    }
+                }
+            }
+        }
+    }
+
+    /// Will use next line help on writing args.
+    fn will_args_wrap(&self, args: &[&Arg], longest: usize) -> bool {
+        args.iter()
+            .filter(|arg| should_show_arg(self.use_long, *arg))
+            .any(|arg| {
+                let spec_vals = &self.spec_vals(arg);
+                self.arg_next_line_help(arg, spec_vals, longest)
+            })
+    }
+
+    fn arg_next_line_help(&self, arg: &Arg, spec_vals: &str, longest: usize) -> bool {
+        if self.next_line_help || arg.is_next_line_help_set() || self.use_long {
+            // setting_next_line
+            true
+        } else {
+            // force_next_line
+            let h = arg.get_help().unwrap_or_default();
+            let h_w = h.display_width() + display_width(spec_vals);
+            let taken = longest + 12;
+            self.term_w >= taken
+                && (taken as f32 / self.term_w as f32) > 0.40
+                && h_w > (self.term_w - taken)
+        }
+    }
+
+    fn spec_vals(&self, a: &Arg) -> String {
+        debug!("HelpTemplate::spec_vals: a={}", a);
+        let mut spec_vals = Vec::new();
+        #[cfg(feature = "env")]
+        if let Some(ref env) = a.env {
+            if !a.is_hide_env_set() {
+                debug!(
+                    "HelpTemplate::spec_vals: Found environment variable...[{:?}:{:?}]",
+                    env.0, env.1
+                );
+                let env_val = if !a.is_hide_env_values_set() {
+                    format!(
+                        "={}",
+                        env.1
+                            .as_ref()
+                            .map(|s| s.to_string_lossy())
+                            .unwrap_or_default()
+                    )
+                } else {
+                    Default::default()
+                };
+                let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
+                spec_vals.push(env_info);
+            }
+        }
+        if a.is_takes_value_set() && !a.is_hide_default_value_set() && !a.default_vals.is_empty() {
+            debug!(
+                "HelpTemplate::spec_vals: Found default value...[{:?}]",
+                a.default_vals
+            );
+
+            let pvs = a
+                .default_vals
+                .iter()
+                .map(|pvs| pvs.to_string_lossy())
+                .map(|pvs| {
+                    if pvs.contains(char::is_whitespace) {
+                        Cow::from(format!("{:?}", pvs))
+                    } else {
+                        pvs
+                    }
+                })
+                .collect::<Vec<_>>()
+                .join(" ");
+
+            spec_vals.push(format!("[default: {}]", pvs));
+        }
+
+        let als = a
+            .aliases
+            .iter()
+            .filter(|&als| als.1) // visible
+            .map(|als| als.0.as_str()) // name
+            .collect::<Vec<_>>()
+            .join(", ");
+        if !als.is_empty() {
+            debug!("HelpTemplate::spec_vals: Found aliases...{:?}", a.aliases);
+            spec_vals.push(format!("[aliases: {}]", als));
+        }
+
+        let als = a
+            .short_aliases
+            .iter()
+            .filter(|&als| als.1) // visible
+            .map(|&als| als.0.to_string()) // name
+            .collect::<Vec<_>>()
+            .join(", ");
+        if !als.is_empty() {
+            debug!(
+                "HelpTemplate::spec_vals: Found short aliases...{:?}",
+                a.short_aliases
+            );
+            spec_vals.push(format!("[short aliases: {}]", als));
+        }
+
+        let possible_vals = a.get_possible_values();
+        if !(a.is_hide_possible_values_set()
+            || possible_vals.is_empty()
+            || self.use_long && possible_vals.iter().any(PossibleValue::should_show_help))
+        {
+            debug!(
+                "HelpTemplate::spec_vals: Found possible vals...{:?}",
+                possible_vals
+            );
+
+            let pvs = possible_vals
+                .iter()
+                .filter_map(PossibleValue::get_visible_quoted_name)
+                .collect::<Vec<_>>()
+                .join(", ");
+
+            spec_vals.push(format!("[possible values: {}]", pvs));
+        }
+        let connector = if self.use_long { "\n" } else { " " };
+        spec_vals.join(connector)
+    }
+
+    fn header<T: Into<String>>(&mut self, msg: T) {
+        self.writer.header(msg);
+    }
+
+    fn literal<T: Into<String>>(&mut self, msg: T) {
+        self.writer.literal(msg);
+    }
+
+    fn none<T: Into<String>>(&mut self, msg: T) {
+        self.writer.none(msg);
+    }
+
+    fn get_spaces(&self, n: usize) -> String {
+        " ".repeat(n)
+    }
+
+    fn spaces(&mut self, n: usize) {
+        self.none(self.get_spaces(n));
+    }
+}
+
+/// Subcommand handling
+impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
+    /// Writes help for subcommands of a Parser Object to the wrapped stream.
+    fn write_subcommands(&mut self, cmd: &Command) {
+        debug!("HelpTemplate::write_subcommands");
+        // The shortest an arg can legally be is 2 (i.e. '-x')
+        let mut longest = 2;
+        let mut ord_v = Vec::new();
+        for subcommand in cmd
+            .get_subcommands()
+            .filter(|subcommand| should_show_subcommand(subcommand))
+        {
+            let mut styled = StyledStr::new();
+            styled.literal(subcommand.get_name());
+            if let Some(short) = subcommand.get_short_flag() {
+                styled.none(", ");
+                styled.literal(format!("-{}", short));
+            }
+            if let Some(long) = subcommand.get_long_flag() {
+                styled.none(", ");
+                styled.literal(format!("--{}", long));
+            }
+            longest = longest.max(styled.display_width());
+            ord_v.push((subcommand.get_display_order(), styled, subcommand));
+        }
+        ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
+
+        debug!("HelpTemplate::write_subcommands longest = {}", longest);
+
+        let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
+
+        let mut first = true;
+        for (_, sc_str, sc) in ord_v {
+            if first {
+                first = false;
+            } else {
+                self.none("\n");
+            }
+            self.write_subcommand(sc_str, sc, next_line_help, longest);
+        }
+    }
+
+    /// Will use next line help on writing subcommands.
+    fn will_subcommands_wrap<'a>(
+        &self,
+        subcommands: impl IntoIterator<Item = &'a Command>,
+        longest: usize,
+    ) -> bool {
+        subcommands
+            .into_iter()
+            .filter(|&subcommand| should_show_subcommand(subcommand))
+            .any(|subcommand| {
+                let spec_vals = &self.sc_spec_vals(subcommand);
+                self.subcommand_next_line_help(subcommand, spec_vals, longest)
+            })
+    }
+
+    fn write_subcommand(
+        &mut self,
+        sc_str: StyledStr,
+        cmd: &Command,
+        next_line_help: bool,
+        longest: usize,
+    ) {
+        debug!("HelpTemplate::write_subcommand");
+
+        let spec_vals = &self.sc_spec_vals(cmd);
+
+        let about = cmd
+            .get_about()
+            .or_else(|| cmd.get_long_about())
+            .unwrap_or_default();
+
+        self.subcmd(sc_str, next_line_help, longest);
+        self.help(None, about, spec_vals, next_line_help, longest)
+    }
+
+    fn sc_spec_vals(&self, a: &Command) -> String {
+        debug!("HelpTemplate::sc_spec_vals: a={}", a.get_name());
+        let mut spec_vals = vec![];
+
+        let mut short_als = a
+            .get_visible_short_flag_aliases()
+            .map(|a| format!("-{}", a))
+            .collect::<Vec<_>>();
+        let als = a.get_visible_aliases().map(|s| s.to_string());
+        short_als.extend(als);
+        let all_als = short_als.join(", ");
+        if !all_als.is_empty() {
+            debug!(
+                "HelpTemplate::spec_vals: Found aliases...{:?}",
+                a.get_all_aliases().collect::<Vec<_>>()
+            );
+            debug!(
+                "HelpTemplate::spec_vals: Found short flag aliases...{:?}",
+                a.get_all_short_flag_aliases().collect::<Vec<_>>()
+            );
+            spec_vals.push(format!("[aliases: {}]", all_als));
+        }
+
+        spec_vals.join(" ")
+    }
+
+    fn subcommand_next_line_help(&self, cmd: &Command, spec_vals: &str, longest: usize) -> bool {
+        if self.next_line_help | self.use_long {
+            // setting_next_line
+            true
+        } else {
+            // force_next_line
+            let h = cmd.get_about().unwrap_or_default();
+            let h_w = h.display_width() + display_width(spec_vals);
+            let taken = longest + 12;
+            self.term_w >= taken
+                && (taken as f32 / self.term_w as f32) > 0.40
+                && h_w > (self.term_w - taken)
+        }
+    }
+
+    /// Writes subcommand to the wrapped stream.
+    fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
+        let width = sc_str.display_width();
+
+        self.none(TAB);
+        self.writer.extend(sc_str.into_iter());
+        if !next_line_help {
+            self.spaces(width.max(longest + TAB_WIDTH) - width);
+        }
+    }
+}
+
+const NEXT_LINE_INDENT: &str = "        ";
+
+type ArgSortKey = fn(arg: &Arg) -> (usize, String);
+
+fn positional_sort_key(arg: &Arg) -> (usize, String) {
+    (arg.get_index().unwrap_or(0), String::new())
+}
+
+fn option_sort_key(arg: &Arg) -> (usize, String) {
+    // Formatting key like this to ensure that:
+    // 1. Argument has long flags are printed just after short flags.
+    // 2. For two args both have short flags like `-c` and `-C`, the
+    //    `-C` arg is printed just after the `-c` arg
+    // 3. For args without short or long flag, print them at last(sorted
+    //    by arg name).
+    // Example order: -a, -b, -B, -s, --select-file, --select-folder, -x
+
+    let key = if let Some(x) = arg.get_short() {
+        let mut s = x.to_ascii_lowercase().to_string();
+        s.push(if x.is_ascii_lowercase() { '0' } else { '1' });
+        s
+    } else if let Some(x) = arg.get_long() {
+        x.to_string()
+    } else {
+        let mut s = '{'.to_string();
+        s.push_str(arg.id.as_str());
+        s
+    };
+    (arg.get_display_order(), key)
+}
+
+pub(crate) fn dimensions() -> (Option<usize>, Option<usize>) {
+    #[cfg(not(feature = "wrap_help"))]
+    return (None, None);
+
+    #[cfg(feature = "wrap_help")]
+    terminal_size::terminal_size()
+        .map(|(w, h)| (Some(w.0.into()), Some(h.0.into())))
+        .unwrap_or_else(|| (parse_env("COLUMNS"), parse_env("LINES")))
+}
+
+#[cfg(feature = "wrap_help")]
+fn parse_env(var: &str) -> Option<usize> {
+    std::env::var_os(var)?.to_str()?.parse::<usize>().ok()
+}
+
+fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
+    debug!(
+        "should_show_arg: use_long={:?}, arg={}",
+        use_long,
+        arg.get_id()
+    );
+    if arg.is_hide_set() {
+        return false;
+    }
+    (!arg.is_hide_long_help_set() && use_long)
+        || (!arg.is_hide_short_help_set() && !use_long)
+        || arg.is_next_line_help_set()
+}
+
+fn should_show_subcommand(subcommand: &Command) -> bool {
+    !subcommand.is_hide_set()
+}
+
+fn replace_newline_var(styled: &mut StyledStr) {
+    for (_, content) in styled.iter_mut() {
+        *content = content.replace("{n}", "\n");
+    }
+}
+
+fn longest_filter(arg: &Arg) -> bool {
+    arg.is_takes_value_set() || arg.long.is_some() || arg.short.is_none()
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn wrap_help_last_word() {
+        let help = String::from("foo bar baz");
+        assert_eq!(wrap(&help, 5), "foo\nbar\nbaz");
+    }
+
+    #[test]
+    #[cfg(feature = "unicode")]
+    fn display_width_handles_non_ascii() {
+        // Popular Danish tongue-twister, the name of a fruit dessert.
+        let text = "rødgrød med fløde";
+        assert_eq!(display_width(text), 17);
+        // Note that the string width is smaller than the string
+        // length. This is due to the precomposed non-ASCII letters:
+        assert_eq!(text.len(), 20);
+    }
+
+    #[test]
+    #[cfg(feature = "unicode")]
+    fn display_width_handles_emojis() {
+        let text = "😂";
+        // There is a single `char`...
+        assert_eq!(text.chars().count(), 1);
+        // but it is double-width:
+        assert_eq!(display_width(text), 2);
+        // This is much less than the byte length:
+        assert_eq!(text.len(), 4);
+    }
+}
diff --git a/src/output/mod.rs b/src/output/mod.rs
index cf262bbe7cd..b3587116f1b 100644
--- a/src/output/mod.rs
+++ b/src/output/mod.rs
@@ -1,13 +1,23 @@
 mod help;
+#[cfg(feature = "help")]
+mod help_template;
 mod usage;
 
 pub(crate) mod fmt;
+#[cfg(feature = "help")]
 pub(crate) mod textwrap;
 
-pub(crate) use self::help::Help;
+pub(crate) use self::help::write_help;
+#[cfg(feature = "help")]
+pub(crate) use self::help_template::AutoHelp;
+#[cfg(feature = "help")]
+pub(crate) use self::help_template::HelpTemplate;
+#[cfg(feature = "help")]
 pub(crate) use self::textwrap::core::display_width;
+#[cfg(feature = "help")]
 pub(crate) use self::textwrap::wrap;
 pub(crate) use self::usage::Usage;
 
 pub(crate) const TAB: &str = "  ";
+#[cfg(feature = "help")]
 pub(crate) const TAB_WIDTH: usize = TAB.len();
diff --git a/src/output/usage.rs b/src/output/usage.rs
index 694a306ee2b..4e7cf3ba98b 100644
--- a/src/output/usage.rs
+++ b/src/output/usage.rs
@@ -1,3 +1,8 @@
+#![cfg_attr(not(feature = "usage"), allow(unused_imports))]
+#![cfg_attr(not(feature = "usage"), allow(unused_variables))]
+#![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))]
+#![cfg_attr(not(feature = "usage"), allow(dead_code))]
+
 // Internal
 use crate::builder::StyledStr;
 use crate::builder::{ArgPredicate, Command};
@@ -28,27 +33,42 @@ impl<'cmd> Usage<'cmd> {
 
     // Creates a usage string for display. This happens just after all arguments were parsed, but before
     // any subcommands have been parsed (so as to give subcommands their own usage recursively)
-    pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> StyledStr {
+    pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> {
         debug!("Usage::create_usage_with_title");
+        let usage = self.create_usage_no_title(used)?;
+
         let mut styled = StyledStr::new();
         styled.header("Usage:");
         styled.none(" ");
-        styled.extend(self.create_usage_no_title(used).into_iter());
-        styled
+        styled.extend(usage.into_iter());
+        Some(styled)
     }
 
     // Creates a usage string (*without title*) if one was not provided by the user manually.
-    pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> StyledStr {
+    pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> {
         debug!("Usage::create_usage_no_title");
         if let Some(u) = self.cmd.get_override_usage() {
-            u.clone()
-        } else if used.is_empty() {
-            self.create_help_usage(true)
+            Some(u.clone())
         } else {
-            self.create_smart_usage(used)
+            #[cfg(feature = "usage")]
+            {
+                if used.is_empty() {
+                    Some(self.create_help_usage(true))
+                } else {
+                    Some(self.create_smart_usage(used))
+                }
+            }
+
+            #[cfg(not(feature = "usage"))]
+            {
+                None
+            }
         }
     }
+}
 
+#[cfg(feature = "usage")]
+impl<'cmd> Usage<'cmd> {
     // Creates a usage string for display in help messages (i.e. not for errors)
     fn create_help_usage(&self, incl_reqs: bool) -> StyledStr {
         debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs);
@@ -175,7 +195,7 @@ impl<'cmd> Usage<'cmd> {
     }
 
     pub(crate) fn get_args(&self, incls: &[Id], force_optional: bool) -> Vec<StyledStr> {
-        debug!("Usage::get_required_usage_from: incls={:?}", incls,);
+        debug!("Usage::get_args: incls={:?}", incls,);
 
         let required_owned;
         let required = if let Some(required) = self.required {
@@ -204,10 +224,7 @@ impl<'cmd> Usage<'cmd> {
             // by unroll_requirements_for_arg.
             unrolled_reqs.push(a.clone());
         }
-        debug!(
-            "Usage::get_required_usage_from: unrolled_reqs={:?}",
-            unrolled_reqs
-        );
+        debug!("Usage::get_args: unrolled_reqs={:?}", unrolled_reqs);
 
         let mut required_groups_members = FlatSet::new();
         let mut required_groups = FlatSet::new();
@@ -292,7 +309,7 @@ impl<'cmd> Usage<'cmd> {
             ret_val.push(pos);
         }
 
-        debug!("Usage::get_required_usage_from: ret_val={:?}", ret_val);
+        debug!("Usage::get_args: ret_val={:?}", ret_val);
         ret_val
     }
 
diff --git a/src/parser/validator.rs b/src/parser/validator.rs
index 4703cb504d5..de0f91084a1 100644
--- a/src/parser/validator.rs
+++ b/src/parser/validator.rs
@@ -183,7 +183,11 @@ impl<'cmd> Validator<'cmd> {
         ))
     }
 
-    fn build_conflict_err_usage(&self, matcher: &ArgMatcher, conflicting_keys: &[Id]) -> StyledStr {
+    fn build_conflict_err_usage(
+        &self,
+        matcher: &ArgMatcher,
+        conflicting_keys: &[Id],
+    ) -> Option<StyledStr> {
         let used_filtered: Vec<Id> = matcher
             .arg_ids()
             .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
@@ -383,9 +387,13 @@ impl<'cmd> Validator<'cmd> {
             && !a.r_unless.iter().any(exists)
     }
 
-    // `incl`: an arg to include in the error even if not used
-    fn missing_required_error(&self, matcher: &ArgMatcher, incl: Vec<Id>) -> ClapResult<()> {
-        debug!("Validator::missing_required_error; incl={:?}", incl);
+    // `req_args`: an arg to include in the error even if not used
+    fn missing_required_error(
+        &self,
+        matcher: &ArgMatcher,
+        raw_req_args: Vec<Id>,
+    ) -> ClapResult<()> {
+        debug!("Validator::missing_required_error; incl={:?}", raw_req_args);
         debug!(
             "Validator::missing_required_error: reqs={:?}",
             self.required
@@ -393,11 +401,32 @@ impl<'cmd> Validator<'cmd> {
 
         let usg = Usage::new(self.cmd).required(&self.required);
 
-        let req_args = usg
-            .get_required_usage_from(&incl, Some(matcher), true)
-            .into_iter()
-            .map(|s| s.to_string())
-            .collect::<Vec<_>>();
+        let req_args = {
+            #[cfg(feature = "usage")]
+            {
+                usg.get_required_usage_from(&raw_req_args, Some(matcher), true)
+                    .into_iter()
+                    .map(|s| s.to_string())
+                    .collect::<Vec<_>>()
+            }
+
+            #[cfg(not(feature = "usage"))]
+            {
+                raw_req_args
+                    .iter()
+                    .map(|id| {
+                        if let Some(arg) = self.cmd.find(id) {
+                            arg.to_string()
+                        } else if let Some(_group) = self.cmd.find_group(id) {
+                            self.cmd.format_group(id).to_string()
+                        } else {
+                            debug_assert!(false, "id={:?} is unknown", id);
+                            "".to_owned()
+                        }
+                    })
+                    .collect::<Vec<_>>()
+            }
+        };
 
         debug!(
             "Validator::missing_required_error: req_args={:#?}",
@@ -412,7 +441,7 @@ impl<'cmd> Validator<'cmd> {
                 self.cmd.find(n).map_or(true, |a| !a.is_hide_set())
             })
             .cloned()
-            .chain(incl)
+            .chain(raw_req_args)
             .collect();
 
         Err(Error::missing_required_argument(


Test patch:
diff --git a/tests/builder/conflicts.rs b/tests/builder/conflicts.rs
index c3b062f3aac..44393ef4955 100644
--- a/tests/builder/conflicts.rs
+++ b/tests/builder/conflicts.rs
@@ -1,7 +1,8 @@
-use super::utils;
-
 use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
 
+#[cfg(feature = "error-context")]
+use super::utils;
+
 #[test]
 fn flag_conflict() {
     let result = Command::new("flag_conflict")
@@ -271,6 +272,7 @@ fn get_arg_conflicts_with_group() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn conflict_output() {
     static CONFLICT_ERR: &str = "\
 error: The argument '--flag...' cannot be used with '-F'
@@ -289,6 +291,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn conflict_output_rev() {
     static CONFLICT_ERR_REV: &str = "\
 error: The argument '-F' cannot be used with '--flag...'
@@ -307,6 +310,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn conflict_output_with_required() {
     static CONFLICT_ERR: &str = "\
 error: The argument '--flag...' cannot be used with '-F'
@@ -325,6 +329,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn conflict_output_rev_with_required() {
     static CONFLICT_ERR_REV: &str = "\
 error: The argument '-F' cannot be used with '--flag...'
@@ -343,6 +348,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn conflict_output_three_conflicting() {
     static CONFLICT_ERR_THREE: &str = "\
 error: The argument '--one' cannot be used with:
@@ -382,6 +388,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn two_conflicting_arguments() {
     let a = Command::new("two_conflicting_arguments")
         .arg(
@@ -409,6 +416,7 @@ fn two_conflicting_arguments() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn three_conflicting_arguments() {
     let a = Command::new("three_conflicting_arguments")
         .arg(
diff --git a/tests/builder/default_missing_vals.rs b/tests/builder/default_missing_vals.rs
index cf67d85cda9..5a3d2d29a67 100644
--- a/tests/builder/default_missing_vals.rs
+++ b/tests/builder/default_missing_vals.rs
@@ -260,6 +260,7 @@ fn delimited_missing_value() {
 
 #[cfg(debug_assertions)]
 #[test]
+#[cfg(feature = "error-context")]
 #[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: \"value\" isn't a valid value for '[arg]'"]
 fn default_missing_values_are_possible_values() {
     use clap::{Arg, Command};
@@ -275,6 +276,7 @@ fn default_missing_values_are_possible_values() {
 
 #[cfg(debug_assertions)]
 #[test]
+#[cfg(feature = "error-context")]
 #[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: Invalid value \"value\" for '[arg]"]
 fn default_missing_values_are_valid() {
     use clap::{Arg, Command};
diff --git a/tests/builder/default_vals.rs b/tests/builder/default_vals.rs
index 0cd6763061d..e39ae76f978 100644
--- a/tests/builder/default_vals.rs
+++ b/tests/builder/default_vals.rs
@@ -1,9 +1,13 @@
 use std::ffi::OsStr;
 use std::ffi::OsString;
 
-use super::utils;
 use clap::builder::ArgPredicate;
-use clap::{arg, error::ErrorKind, value_parser, Arg, ArgAction, Command};
+#[cfg(feature = "error-context")]
+use clap::error::ErrorKind;
+use clap::{arg, value_parser, Arg, ArgAction, Command};
+
+#[cfg(feature = "error-context")]
+use super::utils;
 
 #[test]
 fn opts() {
@@ -30,6 +34,7 @@ fn default_has_index() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn opt_without_value_fail() {
     let r = Command::new("df")
         .arg(
@@ -694,6 +699,7 @@ fn multiple_defaults_override() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn default_vals_donnot_show_in_smart_usage() {
     let cmd = Command::new("bug")
         .arg(
@@ -773,6 +779,7 @@ fn required_args_with_default_values() {
 
 #[cfg(debug_assertions)]
 #[test]
+#[cfg(feature = "error-context")]
 #[should_panic = "Argument `arg`'s default_value=\"value\" failed validation: error: \"value\" isn't a valid value for '[arg]'"]
 fn default_values_are_possible_values() {
     use clap::{Arg, Command};
@@ -788,6 +795,7 @@ fn default_values_are_possible_values() {
 
 #[cfg(debug_assertions)]
 #[test]
+#[cfg(feature = "error-context")]
 #[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"]
 fn invalid_default_values() {
     use clap::{Arg, Command};
@@ -817,6 +825,7 @@ fn valid_delimited_default_values() {
 
 #[cfg(debug_assertions)]
 #[test]
+#[cfg(feature = "error-context")]
 #[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"]
 fn invalid_delimited_default_values() {
     use clap::{Arg, Command};
diff --git a/tests/builder/double_require.rs b/tests/builder/double_require.rs
index d191ac9865b..7c617b42978 100644
--- a/tests/builder/double_require.rs
+++ b/tests/builder/double_require.rs
@@ -10,24 +10,6 @@ Options:
   -h, --help  Print help information
 ";
 
-static ONLY_B_ERROR: &str = "\
-error: The following required arguments were not provided:
-  -c
-
-Usage: prog -b -c
-
-For more information try '--help'
-";
-
-static ONLY_C_ERROR: &str = "\
-error: The following required arguments were not provided:
-  -b
-
-Usage: prog -c -b
-
-For more information try '--help'
-";
-
 fn cmd() -> Command {
     Command::new("prog")
         .arg(
@@ -74,13 +56,32 @@ fn help_text() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn no_duplicate_error() {
+    static ONLY_B_ERROR: &str = "\
+error: The following required arguments were not provided:
+  -c
+
+Usage: prog -b -c
+
+For more information try '--help'
+";
+
     let res = cmd().try_get_matches_from(vec!["", "-b"]);
     assert!(res.is_err());
     let err = res.unwrap_err();
     assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
     assert_eq!(err.to_string(), ONLY_B_ERROR);
 
+    static ONLY_C_ERROR: &str = "\
+error: The following required arguments were not provided:
+  -b
+
+Usage: prog -c -b
+
+For more information try '--help'
+";
+
     let res = cmd().try_get_matches_from(vec!["", "-c"]);
     assert!(res.is_err());
     let err = res.unwrap_err();
diff --git a/tests/builder/empty_values.rs b/tests/builder/empty_values.rs
index 38ffb293b2e..02aa7cf2fb4 100644
--- a/tests/builder/empty_values.rs
+++ b/tests/builder/empty_values.rs
@@ -1,7 +1,8 @@
-use super::utils;
-
 use clap::{error::ErrorKind, Arg, ArgAction, Command};
 
+#[cfg(feature = "error-context")]
+use super::utils;
+
 #[test]
 fn empty_values() {
     let m = Command::new("config")
@@ -102,6 +103,7 @@ fn no_empty_values_without_equals() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn no_empty_values_without_equals_but_requires_equals() {
     let cmd = Command::new("config").arg(
         Arg::new("config")
diff --git a/tests/builder/error.rs b/tests/builder/error.rs
index 2db329d80b3..bbdfa73598f 100644
--- a/tests/builder/error.rs
+++ b/tests/builder/error.rs
@@ -79,11 +79,11 @@ fn value_validation_has_newline() {
 }
 
 #[test]
-fn null_prints_help() {
+fn kind_prints_help() {
     let cmd = Command::new("test");
     let res = cmd
         .try_get_matches_from(["test", "--help"])
-        .map_err(|e| e.apply::<clap::error::NullFormatter>());
+        .map_err(|e| e.apply::<clap::error::KindFormatter>());
     assert!(res.is_err());
     let err = res.unwrap_err();
     let expected_kind = ErrorKind::DisplayHelp;
@@ -97,6 +97,7 @@ Options:
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn raw_prints_help() {
     let cmd = Command::new("test");
     let res = cmd
@@ -115,19 +116,22 @@ Options:
 }
 
 #[test]
-fn null_ignores_validation_error() {
+fn kind_formats_validation_error() {
     let cmd = Command::new("test");
     let res = cmd
         .try_get_matches_from(["test", "unused"])
-        .map_err(|e| e.apply::<clap::error::NullFormatter>());
+        .map_err(|e| e.apply::<clap::error::KindFormatter>());
     assert!(res.is_err());
     let err = res.unwrap_err();
     let expected_kind = ErrorKind::UnknownArgument;
-    static MESSAGE: &str = "";
+    static MESSAGE: &str = "\
+error: Found an argument which wasn't expected or isn't valid in this context
+";
     assert_error(err, expected_kind, MESSAGE, true);
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn rich_formats_validation_error() {
     let cmd = Command::new("test");
     let res = cmd.try_get_matches_from(["test", "unused"]);
@@ -145,6 +149,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn raw_formats_validation_error() {
     let cmd = Command::new("test");
     let res = cmd
diff --git a/tests/builder/flags.rs b/tests/builder/flags.rs
index e92d97571fc..0f4d316f557 100644
--- a/tests/builder/flags.rs
+++ b/tests/builder/flags.rs
@@ -1,15 +1,7 @@
-use super::utils;
 use clap::{arg, Arg, ArgAction, Command};
 
-const USE_FLAG_AS_ARGUMENT: &str = "\
-error: Found argument '--another-flag' which wasn't expected, or isn't valid in this context
-
-  If you tried to supply `--another-flag` as a value rather than a flag, use `-- --another-flag`
-
-Usage: mycat [OPTIONS] [filename]
-
-For more information try '--help'
-";
+#[cfg(feature = "error-context")]
+use super::utils;
 
 #[test]
 fn flag_using_short() {
@@ -143,7 +135,18 @@ fn multiple_flags_in_single() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn issue_1284_argument_in_flag_style() {
+    const USE_FLAG_AS_ARGUMENT: &str = "\
+error: Found argument '--another-flag' which wasn't expected, or isn't valid in this context
+
+  If you tried to supply `--another-flag` as a value rather than a flag, use `-- --another-flag`
+
+Usage: mycat [OPTIONS] [filename]
+
+For more information try '--help'
+";
+
     let cmd = Command::new("mycat")
         .arg(Arg::new("filename"))
         .arg(Arg::new("a-flag").long("a-flag").action(ArgAction::SetTrue));
@@ -176,6 +179,7 @@ fn issue_1284_argument_in_flag_style() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn issue_2308_multiple_dashes() {
     static MULTIPLE_DASHES: &str = "\
 error: Found argument '-----' which wasn't expected, or isn't valid in this context
diff --git a/tests/builder/groups.rs b/tests/builder/groups.rs
index 99ac9fec94f..085996b63ba 100644
--- a/tests/builder/groups.rs
+++ b/tests/builder/groups.rs
@@ -1,30 +1,6 @@
-use super::utils;
-
 use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command, Id};
 
-static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided:
-  <base|--delete>
-
-Usage: clap-test <base|--delete>
-
-For more information try '--help'
-";
-
-static REQ_GROUP_CONFLICT_USAGE: &str = "\
-error: The argument '--delete' cannot be used with '[base]'
-
-Usage: clap-test <base|--delete>
-
-For more information try '--help'
-";
-
-static REQ_GROUP_CONFLICT_ONLY_OPTIONS: &str = "\
-error: The argument '--delete' cannot be used with '--all'
-
-Usage: clap-test <--all|--delete>
-
-For more information try '--help'
-";
+use super::utils;
 
 #[test]
 fn required_group_missing_arg() {
@@ -150,7 +126,16 @@ fn empty_group() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn req_group_usage_string() {
+    static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided:
+  <base|--delete>
+
+Usage: clap-test <base|--delete>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("req_group")
         .arg(arg!([base] "Base commit"))
         .arg(arg!(
@@ -166,7 +151,16 @@ fn req_group_usage_string() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn req_group_with_conflict_usage_string() {
+    static REQ_GROUP_CONFLICT_USAGE: &str = "\
+error: The argument '--delete' cannot be used with '[base]'
+
+Usage: clap-test <base|--delete>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("req_group")
         .arg(arg!([base] "Base commit").conflicts_with("delete"))
         .arg(arg!(
@@ -187,7 +181,16 @@ fn req_group_with_conflict_usage_string() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn req_group_with_conflict_usage_string_only_options() {
+    static REQ_GROUP_CONFLICT_ONLY_OPTIONS: &str = "\
+error: The argument '--delete' cannot be used with '--all'
+
+Usage: clap-test <--all|--delete>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("req_group")
         .arg(arg!(-a --all "All").conflicts_with("delete"))
         .arg(arg!(
diff --git a/tests/builder/help.rs b/tests/builder/help.rs
index fdb94799842..05e292085f9 100644
--- a/tests/builder/help.rs
+++ b/tests/builder/help.rs
@@ -1,7 +1,9 @@
-use super::utils;
+#![cfg(feature = "help")]
 
 use clap::{arg, builder::PossibleValue, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
 
+use super::utils;
+
 fn setup() -> Command {
     Command::new("test")
         .author("Kevin K.")
@@ -52,6 +54,7 @@ fn help_subcommand() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn help_multi_subcommand_error() {
     let cmd = Command::new("ctest").subcommand(
         Command::new("subcmd").subcommand(
diff --git a/tests/builder/main.rs b/tests/builder/main.rs
index b302980dc99..858f287549c 100644
--- a/tests/builder/main.rs
+++ b/tests/builder/main.rs
@@ -1,5 +1,7 @@
 #![allow(clippy::bool_assert_comparison)]
 #![allow(clippy::redundant_clone)]
+#![cfg(feature = "help")]
+#![cfg(feature = "usage")]
 
 mod action;
 mod app_settings;
diff --git a/tests/builder/opts.rs b/tests/builder/opts.rs
index 8c7a836ede4..2771306f038 100644
--- a/tests/builder/opts.rs
+++ b/tests/builder/opts.rs
@@ -1,32 +1,7 @@
-use super::utils;
-
 use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgMatches, Command};
 
-#[cfg(feature = "suggestions")]
-static DYM: &str = "\
-error: Found argument '--optio' which wasn't expected, or isn't valid in this context
-
-  Did you mean '--option'?
-
-  If you tried to supply `--optio` as a value rather than a flag, use `-- --optio`
-
-Usage: clap-test --option <opt>... [positional] [positional2] [positional3]...
-
-For more information try '--help'
-";
-
-#[cfg(feature = "suggestions")]
-static DYM_ISSUE_1073: &str = "\
-error: Found argument '--files-without-matches' which wasn't expected, or isn't valid in this context
-
-  Did you mean '--files-without-match'?
-
-  If you tried to supply `--files-without-matches` as a value rather than a flag, use `-- --files-without-matches`
-
-Usage: ripgrep-616 --files-without-match
-
-For more information try '--help'
-";
+#[cfg(feature = "error-context")]
+use super::utils;
 
 #[test]
 fn require_equals_fail() {
@@ -44,6 +19,7 @@ fn require_equals_fail() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn require_equals_fail_message() {
     static NO_EQUALS: &str =
         "error: Equal sign is needed when assigning values to '--config=<cfg>'.
@@ -468,7 +444,20 @@ fn leading_hyphen_with_only_pos_follows() {
 
 #[test]
 #[cfg(feature = "suggestions")]
+#[cfg(feature = "error-context")]
 fn did_you_mean() {
+    static DYM: &str = "\
+error: Found argument '--optio' which wasn't expected, or isn't valid in this context
+
+  Did you mean '--option'?
+
+  If you tried to supply `--optio` as a value rather than a flag, use `-- --optio`
+
+Usage: clap-test --option <opt>... [positional] [positional2] [positional3]...
+
+For more information try '--help'
+";
+
     utils::assert_output(utils::complex_app(), "clap-test --optio=foo", DYM, true);
 }
 
@@ -555,7 +544,20 @@ fn issue_1105_empty_value_short_explicit_no_space() {
 
 #[test]
 #[cfg(feature = "suggestions")]
+#[cfg(feature = "error-context")]
 fn issue_1073_suboptimal_flag_suggestion() {
+    static DYM_ISSUE_1073: &str = "\
+error: Found argument '--files-without-matches' which wasn't expected, or isn't valid in this context
+
+  Did you mean '--files-without-match'?
+
+  If you tried to supply `--files-without-matches` as a value rather than a flag, use `-- --files-without-matches`
+
+Usage: ripgrep-616 --files-without-match
+
+For more information try '--help'
+";
+
     let cmd = Command::new("ripgrep-616")
         .arg(
             Arg::new("files-with-matches")
diff --git a/tests/builder/possible_values.rs b/tests/builder/possible_values.rs
index 3bca2ffd55e..17ab980a635 100644
--- a/tests/builder/possible_values.rs
+++ b/tests/builder/possible_values.rs
@@ -1,42 +1,7 @@
-use super::utils;
-
 use clap::{builder::PossibleValue, error::ErrorKind, Arg, ArgAction, Command};
 
-#[cfg(feature = "suggestions")]
-static PV_ERROR: &str = "\
-error: \"slo\" isn't a valid value for '-O <option>'
-  [possible values: slow, fast, \"ludicrous speed\"]
-
-  Did you mean \"slow\"?
-
-For more information try '--help'
-";
-
-#[cfg(not(feature = "suggestions"))]
-static PV_ERROR: &str = "\
-error: \"slo\" isn't a valid value for '-O <option>'
-  [possible values: slow, fast, \"ludicrous speed\"]
-
-For more information try '--help'
-";
-
-#[cfg(feature = "suggestions")]
-static PV_ERROR_ESCAPED: &str = "\
-error: \"ludicrous\" isn't a valid value for '-O <option>'
-  [possible values: slow, fast, \"ludicrous speed\"]
-
-  Did you mean \"ludicrous speed\"?
-
-For more information try '--help'
-";
-
-#[cfg(not(feature = "suggestions"))]
-static PV_ERROR_ESCAPED: &str = "\
-error: \"ludicrous\" isn't a valid value for '-O <option>'
-  [possible values: slow, fast, \"ludicrous speed\"]
-
-For more information try '--help'
-";
+#[cfg(feature = "error-context")]
+use super::utils;
 
 #[test]
 fn possible_values_of_positional() {
@@ -209,7 +174,26 @@ fn possible_values_of_option_multiple_fail() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn possible_values_output() {
+    #[cfg(feature = "suggestions")]
+    static PV_ERROR: &str = "\
+error: \"slo\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+  Did you mean \"slow\"?
+
+For more information try '--help'
+";
+
+    #[cfg(not(feature = "suggestions"))]
+    static PV_ERROR: &str = "\
+error: \"slo\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+For more information try '--help'
+";
+
     utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
@@ -224,7 +208,26 @@ fn possible_values_output() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn possible_values_alias_output() {
+    #[cfg(feature = "suggestions")]
+    static PV_ERROR: &str = "\
+error: \"slo\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+  Did you mean \"slow\"?
+
+For more information try '--help'
+";
+
+    #[cfg(not(feature = "suggestions"))]
+    static PV_ERROR: &str = "\
+error: \"slo\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+For more information try '--help'
+";
+
     utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
@@ -243,7 +246,26 @@ fn possible_values_alias_output() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn possible_values_hidden_output() {
+    #[cfg(feature = "suggestions")]
+    static PV_ERROR: &str = "\
+error: \"slo\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+  Did you mean \"slow\"?
+
+For more information try '--help'
+";
+
+    #[cfg(not(feature = "suggestions"))]
+    static PV_ERROR: &str = "\
+error: \"slo\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+For more information try '--help'
+";
+
     utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
@@ -263,7 +285,26 @@ fn possible_values_hidden_output() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn escaped_possible_values_output() {
+    #[cfg(feature = "suggestions")]
+    static PV_ERROR_ESCAPED: &str = "\
+error: \"ludicrous\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+  Did you mean \"ludicrous speed\"?
+
+For more information try '--help'
+";
+
+    #[cfg(not(feature = "suggestions"))]
+    static PV_ERROR_ESCAPED: &str = "\
+error: \"ludicrous\" isn't a valid value for '-O <option>'
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+For more information try '--help'
+";
+
     utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
@@ -278,7 +319,15 @@ fn escaped_possible_values_output() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn missing_possible_value_error() {
+    static MISSING_PV_ERROR: &str = "\
+error: The argument '-O <option>' requires a value but none was supplied
+  [possible values: slow, fast, \"ludicrous speed\"]
+
+For more information try '--help'
+";
+
     utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
@@ -297,13 +346,6 @@ fn missing_possible_value_error() {
     );
 }
 
-static MISSING_PV_ERROR: &str = "\
-error: The argument '-O <option>' requires a value but none was supplied
-  [possible values: slow, fast, \"ludicrous speed\"]
-
-For more information try '--help'
-";
-
 #[test]
 fn alias() {
     let m = Command::new("pv")
diff --git a/tests/builder/require.rs b/tests/builder/require.rs
index 1a589a0624b..915b89076a0 100644
--- a/tests/builder/require.rs
+++ b/tests/builder/require.rs
@@ -1,54 +1,8 @@
-use super::utils;
-
 use clap::builder::ArgPredicate;
 use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
 
-static REQUIRE_EQUALS: &str = "\
-error: The following required arguments were not provided:
-  --opt=<FILE>
-
-Usage: clap-test --opt=<FILE>
-
-For more information try '--help'
-";
-
-static REQUIRE_EQUALS_FILTERED: &str = "\
-error: The following required arguments were not provided:
-  --opt=<FILE>
-
-Usage: clap-test --opt=<FILE> --foo=<FILE>
-
-For more information try '--help'
-";
-
-static REQUIRE_EQUALS_FILTERED_GROUP: &str = "\
-error: The following required arguments were not provided:
-  --opt=<FILE>
-
-Usage: clap-test --opt=<FILE> --foo=<FILE> <--g1=<FILE>|--g2=<FILE>>
-
-For more information try '--help'
-";
-
-static MISSING_REQ: &str = "\
-error: The following required arguments were not provided:
-  --long-option-2 <option2>
-  <positional>
-  <positional2>
-
-Usage: clap-test --long-option-2 <option2> -F <positional> <positional2> [positional3]...
-
-For more information try '--help'
-";
-
-static COND_REQ_IN_USAGE: &str = "\
-error: The following required arguments were not provided:
-  --output <output>
-
-Usage: test --target <target> --input <input> --output <output>
-
-For more information try '--help'
-";
+#[cfg(feature = "error-context")]
+use super::utils;
 
 #[test]
 fn flag_required() {
@@ -127,16 +81,9 @@ fn positional_required_2() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn positional_required_with_requires() {
-    let cmd = Command::new("positional_required")
-        .arg(Arg::new("flag").required(true).requires("opt"))
-        .arg(Arg::new("opt"))
-        .arg(Arg::new("bar"));
-
-    utils::assert_output(cmd, "clap-test", POSITIONAL_REQ, true);
-}
-
-static POSITIONAL_REQ: &str = "\
+    static POSITIONAL_REQ: &str = "\
 error: The following required arguments were not provided:
   <flag>
   <opt>
@@ -146,17 +93,18 @@ Usage: clap-test <flag> <opt> [bar]
 For more information try '--help'
 ";
 
-#[test]
-fn positional_required_with_requires_if_no_value() {
     let cmd = Command::new("positional_required")
-        .arg(Arg::new("flag").required(true).requires_if("val", "opt"))
+        .arg(Arg::new("flag").required(true).requires("opt"))
         .arg(Arg::new("opt"))
         .arg(Arg::new("bar"));
 
-    utils::assert_output(cmd, "clap-test", POSITIONAL_REQ_IF_NO_VAL, true);
+    utils::assert_output(cmd, "clap-test", POSITIONAL_REQ, true);
 }
 
-static POSITIONAL_REQ_IF_NO_VAL: &str = "\
+#[test]
+#[cfg(feature = "error-context")]
+fn positional_required_with_requires_if_no_value() {
+    static POSITIONAL_REQ_IF_NO_VAL: &str = "\
 error: The following required arguments were not provided:
   <flag>
 
@@ -165,18 +113,18 @@ Usage: clap-test <flag> [opt] [bar]
 For more information try '--help'
 ";
 
-#[test]
-fn positional_required_with_requires_if_value() {
     let cmd = Command::new("positional_required")
         .arg(Arg::new("flag").required(true).requires_if("val", "opt"))
-        .arg(Arg::new("foo").required(true))
         .arg(Arg::new("opt"))
         .arg(Arg::new("bar"));
 
-    utils::assert_output(cmd, "clap-test val", POSITIONAL_REQ_IF_VAL, true);
+    utils::assert_output(cmd, "clap-test", POSITIONAL_REQ_IF_NO_VAL, true);
 }
 
-static POSITIONAL_REQ_IF_VAL: &str = "\
+#[test]
+#[cfg(feature = "error-context")]
+fn positional_required_with_requires_if_value() {
+    static POSITIONAL_REQ_IF_VAL: &str = "\
 error: The following required arguments were not provided:
   <foo>
   <opt>
@@ -186,6 +134,15 @@ Usage: clap-test <flag> <foo> <opt> [bar]
 For more information try '--help'
 ";
 
+    let cmd = Command::new("positional_required")
+        .arg(Arg::new("flag").required(true).requires_if("val", "opt"))
+        .arg(Arg::new("foo").required(true))
+        .arg(Arg::new("opt"))
+        .arg(Arg::new("bar"));
+
+    utils::assert_output(cmd, "clap-test val", POSITIONAL_REQ_IF_VAL, true);
+}
+
 #[test]
 fn group_required() {
     let result = Command::new("group_required")
@@ -553,7 +510,19 @@ fn required_unless_any_err() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn missing_required_output() {
+    static MISSING_REQ: &str = "\
+error: The following required arguments were not provided:
+  --long-option-2 <option2>
+  <positional>
+  <positional2>
+
+Usage: clap-test --long-option-2 <option2> -F <positional> <positional2> [positional3]...
+
+For more information try '--help'
+";
+
     utils::assert_output(utils::complex_app(), "clap-test -F", MISSING_REQ, true);
 }
 
@@ -786,7 +755,17 @@ fn required_if_any_all_values_present_fail() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn list_correct_required_args() {
+    static COND_REQ_IN_USAGE: &str = "\
+error: The following required arguments were not provided:
+  --output <output>
+
+Usage: test --target <target> --input <input> --output <output>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("Test cmd")
         .version("1.0")
         .author("F0x06")
@@ -820,7 +799,17 @@ fn list_correct_required_args() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn required_if_val_present_fail_error_output() {
+    static COND_REQ_IN_USAGE: &str = "\
+error: The following required arguments were not provided:
+  --output <output>
+
+Usage: test --target <target> --input <input> --output <output>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("Test cmd")
         .version("1.0")
         .author("F0x06")
@@ -935,7 +924,17 @@ fn required_ifs_wrong_val_mult_fail() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn require_eq() {
+    static REQUIRE_EQUALS: &str = "\
+error: The following required arguments were not provided:
+  --opt=<FILE>
+
+Usage: clap-test --opt=<FILE>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("clap-test").version("v1.4.8").arg(
         Arg::new("opt")
             .long("opt")
@@ -949,7 +948,17 @@ fn require_eq() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn require_eq_filtered() {
+    static REQUIRE_EQUALS_FILTERED: &str = "\
+error: The following required arguments were not provided:
+  --opt=<FILE>
+
+Usage: clap-test --opt=<FILE> --foo=<FILE>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("clap-test")
         .version("v1.4.8")
         .arg(
@@ -974,7 +983,17 @@ fn require_eq_filtered() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn require_eq_filtered_group() {
+    static REQUIRE_EQUALS_FILTERED_GROUP: &str = "\
+error: The following required arguments were not provided:
+  --opt=<FILE>
+
+Usage: clap-test --opt=<FILE> --foo=<FILE> <--g1=<FILE>|--g2=<FILE>>
+
+For more information try '--help'
+";
+
     let cmd = Command::new("clap-test")
         .version("v1.4.8")
         .arg(
@@ -1020,17 +1039,6 @@ fn require_eq_filtered_group() {
     );
 }
 
-static ISSUE_1158: &str = "\
-error: The following required arguments were not provided:
-  -x <X>
-  -y <Y>
-  -z <Z>
-
-Usage: example -x <X> -y <Y> -z <Z> <ID>
-
-For more information try '--help'
-";
-
 fn issue_1158_app() -> Command {
     Command::new("example")
         .arg(
@@ -1054,6 +1062,7 @@ fn issue_1158_app() -> Command {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn multiple_required_unless_usage_printing() {
     static MULTIPLE_REQUIRED_UNLESS_USAGE: &str = "\
 error: The following required arguments were not provided:
@@ -1097,7 +1106,19 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn issue_1158_conflicting_requirements() {
+    static ISSUE_1158: &str = "\
+error: The following required arguments were not provided:
+  -x <X>
+  -y <Y>
+  -z <Z>
+
+Usage: example -x <X> -y <Y> -z <Z> <ID>
+
+For more information try '--help'
+";
+
     let cmd = issue_1158_app();
 
     utils::assert_output(cmd, "example id", ISSUE_1158, true);
@@ -1409,6 +1430,7 @@ fn required_unless_all_on_default_value() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn required_error_doesnt_duplicate() {
     let cmd = Command::new("Clap-created-USAGE-string-bug")
         .arg(Arg::new("a").required(true))
@@ -1435,6 +1457,7 @@ For more information try '--help'
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn required_require_with_group_shows_flag() {
     let cmd = Command::new("test")
         .arg(arg!(--"require-first").requires("first"))
diff --git a/tests/builder/subcommands.rs b/tests/builder/subcommands.rs
index f8e8a181712..96796ed78aa 100644
--- a/tests/builder/subcommands.rs
+++ b/tests/builder/subcommands.rs
@@ -1,66 +1,6 @@
-use super::utils;
-
 use clap::{arg, error::ErrorKind, Arg, ArgAction, Command};
 
-static VISIBLE_ALIAS_HELP: &str = "\
-Usage: clap-test [COMMAND]
-
-Commands:
-  test  Some help [aliases: dongle, done]
-  help  Print this message or the help of the given subcommand(s)
-
-Options:
-  -h, --help     Print help information
-  -V, --version  Print version information
-";
-
-static INVISIBLE_ALIAS_HELP: &str = "\
-Usage: clap-test [COMMAND]
-
-Commands:
-  test  Some help
-  help  Print this message or the help of the given subcommand(s)
-
-Options:
-  -h, --help     Print help information
-  -V, --version  Print version information
-";
-
-#[cfg(feature = "suggestions")]
-static DYM_SUBCMD: &str = "\
-error: The subcommand 'subcm' wasn't recognized
-
-  Did you mean 'subcmd'?
-
-If you believe you received this message in error, try re-running with 'dym -- subcm'
-
-Usage: dym [COMMAND]
-
-For more information try '--help'
-";
-
-#[cfg(feature = "suggestions")]
-static DYM_SUBCMD_AMBIGUOUS: &str = "\
-error: The subcommand 'te' wasn't recognized
-
-  Did you mean 'test' or 'temp'?
-
-If you believe you received this message in error, try re-running with 'dym -- te'
-
-Usage: dym [COMMAND]
-
-For more information try '--help'
-";
-
-static SUBCMD_AFTER_DOUBLE_DASH: &str = "\
-error: Found argument 'subcmd' which wasn't expected, or isn't valid in this context
-
-  If you tried to supply `subcmd` as a subcommand, remove the '--' before it.
-
-Usage: cmd [COMMAND]
-
-For more information try '--help'
-";
+use super::utils;
 
 #[test]
 fn subcommand() {
@@ -154,14 +94,42 @@ fn multiple_aliases() {
 
 #[test]
 #[cfg(feature = "suggestions")]
+#[cfg(feature = "error-context")]
 fn subcmd_did_you_mean_output() {
+    #[cfg(feature = "suggestions")]
+    static DYM_SUBCMD: &str = "\
+error: The subcommand 'subcm' wasn't recognized
+
+  Did you mean 'subcmd'?
+
+If you believe you received this message in error, try re-running with 'dym -- subcm'
+
+Usage: dym [COMMAND]
+
+For more information try '--help'
+";
+
     let cmd = Command::new("dym").subcommand(Command::new("subcmd"));
     utils::assert_output(cmd, "dym subcm", DYM_SUBCMD, true);
 }
 
 #[test]
 #[cfg(feature = "suggestions")]
+#[cfg(feature = "error-context")]
 fn subcmd_did_you_mean_output_ambiguous() {
+    #[cfg(feature = "suggestions")]
+    static DYM_SUBCMD_AMBIGUOUS: &str = "\
+error: The subcommand 'te' wasn't recognized
+
+  Did you mean 'test' or 'temp'?
+
+If you believe you received this message in error, try re-running with 'dym -- te'
+
+Usage: dym [COMMAND]
+
+For more information try '--help'
+";
+
     let cmd = Command::new("dym")
         .subcommand(Command::new("test"))
         .subcommand(Command::new("temp"));
@@ -170,6 +138,7 @@ fn subcmd_did_you_mean_output_ambiguous() {
 
 #[test]
 #[cfg(feature = "suggestions")]
+#[cfg(feature = "error-context")]
 fn subcmd_did_you_mean_output_arg() {
     static EXPECTED: &str = "\
 error: Found argument '--subcmarg' which wasn't expected, or isn't valid in this context
@@ -191,6 +160,7 @@ For more information try '--help'
 
 #[test]
 #[cfg(feature = "suggestions")]
+#[cfg(feature = "error-context")]
 fn subcmd_did_you_mean_output_arg_false_positives() {
     static EXPECTED: &str = "\
 error: Found argument '--subcmarg' which wasn't expected, or isn't valid in this context
@@ -219,6 +189,18 @@ fn alias_help() {
 
 #[test]
 fn visible_aliases_help_output() {
+    static VISIBLE_ALIAS_HELP: &str = "\
+Usage: clap-test [COMMAND]
+
+Commands:
+  test  Some help [aliases: dongle, done]
+  help  Print this message or the help of the given subcommand(s)
+
+Options:
+  -h, --help     Print help information
+  -V, --version  Print version information
+";
+
     let cmd = Command::new("clap-test").version("2.6").subcommand(
         Command::new("test")
             .about("Some help")
@@ -231,6 +213,18 @@ fn visible_aliases_help_output() {
 
 #[test]
 fn invisible_aliases_help_output() {
+    static INVISIBLE_ALIAS_HELP: &str = "\
+Usage: clap-test [COMMAND]
+
+Commands:
+  test  Some help
+  help  Print this message or the help of the given subcommand(s)
+
+Options:
+  -h, --help     Print help information
+  -V, --version  Print version information
+";
+
     let cmd = Command::new("clap-test")
         .version("2.6")
         .subcommand(Command::new("test").about("Some help").alias("invisible"));
@@ -361,7 +355,18 @@ fn subcommand_placeholder_test() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn subcommand_used_after_double_dash() {
+    static SUBCMD_AFTER_DOUBLE_DASH: &str = "\
+error: Found argument 'subcmd' which wasn't expected, or isn't valid in this context
+
+  If you tried to supply `subcmd` as a subcommand, remove the '--' before it.
+
+Usage: cmd [COMMAND]
+
+For more information try '--help'
+";
+
     let cmd = Command::new("cmd").subcommand(Command::new("subcmd"));
 
     utils::assert_output(cmd, "cmd -- subcmd", SUBCMD_AFTER_DOUBLE_DASH, true);
@@ -421,6 +426,7 @@ fn issue_2494_subcommand_is_present() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn subcommand_not_recognized() {
     let cmd = Command::new("fake")
         .subcommand(Command::new("sub"))
@@ -494,6 +500,7 @@ fn hostname_like_multicall() {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn bad_multicall_command_error() {
     let cmd = Command::new("repl")
         .version("1.0.0")
diff --git a/tests/derive/custom_string_parsers.rs b/tests/derive/custom_string_parsers.rs
index 869af1bf0c9..d4b087d7a25 100644
--- a/tests/derive/custom_string_parsers.rs
+++ b/tests/derive/custom_string_parsers.rs
@@ -68,6 +68,7 @@ struct HexOpt {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn test_parse_hex() {
     assert_eq!(
         HexOpt { number: 5 },
diff --git a/tests/derive/main.rs b/tests/derive/main.rs
index 33b59cdf982..0c6ea7bd75b 100644
--- a/tests/derive/main.rs
+++ b/tests/derive/main.rs
@@ -1,4 +1,6 @@
 #![cfg(feature = "derive")]
+#![cfg(feature = "help")]
+#![cfg(feature = "usage")]
 
 mod app_name;
 mod arguments;
diff --git a/tests/derive/non_literal_attributes.rs b/tests/derive/non_literal_attributes.rs
index 767c3774c26..bc2df92b46a 100644
--- a/tests/derive/non_literal_attributes.rs
+++ b/tests/derive/non_literal_attributes.rs
@@ -136,6 +136,7 @@ struct HexOpt {
 }
 
 #[test]
+#[cfg(feature = "error-context")]
 fn test_parse_hex_function_path() {
     assert_eq!(
         HexOpt { number: 5 },
diff --git a/tests/examples.rs b/tests/examples.rs
index 2dddf81157d..74d6ce110ab 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -1,6 +1,9 @@
 #![cfg(not(tarpaulin))]
 
 #[test]
+#[cfg(feature = "help")]
+#[cfg(feature = "error-context")]
+#[cfg(feature = "usage")]
 fn example_tests() {
     let t = trycmd::TestCases::new();
     let features = [
diff --git a/tests/macros.rs b/tests/macros.rs
index 7d56570f835..e95ea349765 100644
--- a/tests/macros.rs
+++ b/tests/macros.rs
@@ -277,6 +277,7 @@ mod arg {
     }
 
     #[test]
+    #[cfg(all(feature = "help", featiure = "usage"))]
     fn optional_value() {
         let mut cmd = clap::Command::new("test").arg(clap::arg!(port: -p [NUM]));
 
diff --git a/tests/ui.rs b/tests/ui.rs
index 733e821e10c..6603830c85e 100644
--- a/tests/ui.rs
+++ b/tests/ui.rs
@@ -1,6 +1,9 @@
 #![cfg(not(tarpaulin))]
 
 #[test]
+#[cfg(feature = "help")]
+#[cfg(feature = "error-context")]
+#[cfg(feature = "usage")]
 fn ui_tests() {
     let t = trycmd::TestCases::new();
     let features = [