The "group" header doesn't add anything semantically relevant. Are you doing this purely for display purposes?
The simplest thing that is semantically equivalent would be just:
Join[data]
(* <|"on" -> <|"A" -> <|Length -> 19, Total -> 8|>, "B" -> <|Length -> 19, Total -> 8|>, "C" -> <|Length -> 7, Total -> 0|>|>,
"off" -> <|"A" -> <|Length -> 9, Total -> 42|>, "B" -> <|Length -> 7, Total -> 11|>, "C" -> <|Length -> 9, Total -> 27|>|>|> *)
Or wrapping that with Dataset
:

If you really want the "group", you could do something like this:
<|"group" -> #|> & /@ Join[data]
or with Dataset
:
Dataset[<|"group" -> #|> & /@ Join[data]]

If you really want the Length
and Total
headers to be at at the top under group
, then it would take me more time than I'm willing to spend fiddling with it. Dataset
has very strong "opinions" on how things should be displayed. Maybe just transposing Length-Total
with A-B-C
would work. If your main concern is just display, then I'd use some other structure, maybe even just Grid
.