15

Is there a package or simple command that will insert an \hline after every line in a tabular environment? It is rather annoying to do it all manually, and it seems like there should be a simple way of doing this (it seems to be a relatively common thing to want to do).

Is there something that does this?

Edit: In case it does not go without saying, I would also like a line above the first row as well.

Clarification: I currently have something that looks like this:

\documentclass[12pt]{article}
\usepackage[english]{babel}
\usepackage{amsmath,amsthm}
\usepackage{amsfonts}

\begin{document}
\begin{tabular}{l}
\hline
a\\
\hline
a\\
\hline
a\\
\hline
a\\
\hline
a\\
\hline
\end{tabular}

\end{document}

I would like to have something that would remove the need for me to type \hline so many times.

7 Answers 7

12

to make a table line followed by a line the markup is \\\hline That's only 8 characters so it's rather hard to come up with a less intrusive markup, given that you have to mark the end of row manually anyway. Perhaps \def\nl{\\\hline} then you only need the three characters \nl at the end of each row.

4
  • 1
    I am not sure if this is what I am going for. I updated the question to clarify.
    – soandos
    May 1, 2013 at 9:23
  • @soandos Why isn't it what you need? (even though it is not what you asked for) If you are happy to use \\ at the end of a row for an unlined row, surely typing \nl at the end of a row for a lined row is no more of a burden? May 1, 2013 at 9:45
  • I originally misunderstood. It does do what I want, but I was just expecting something else (an environment that did this for me and I just had to use the `\\` ).
    – soandos
    May 1, 2013 at 10:00
  • There is an answer that does (almost) exactly what OP requested in a pre-TeX.SE answer over in StackOverflow make LaTeX draw a \hline between each line in tabular without using \hline?. (I don't understand how it works, though, and will post a question in TeX.SE asking for an explanation.)
    – Mars
    Aug 28, 2013 at 22:26
10

How does this TeX code generate automatic \hline's in tabular environments?

Automatic Horizonal Lines in a Nutshell

  • Intent is to keep unmodified syntax in the tabular environment, yet alter formatting globally.
  • This will change the meaning of \\ for all tabular environments (that use \@tabularcr).
  • \@tabularcr is the macro you are looking for. It is what \\ represents in tabular rows except when using the array package, see Array Package Changes below.

    1. Clone it: \let \clone@tabularcr \@tabularcr
    2. Reset it: \def\@tabularcr{\clone@tabularcr\hline}

Note that you might be better off using \midrule from the booktabs package instead of \hline. Also note that redefining \\ may mean that you might run into difficulties trying to use \bottomrule, because you might end up with undesired double lines below the last row. There is a way to detect the last row, but it is finicky. I will add it when I get around to it.

Code

\documentclass{article}
\usepackage{fontspec}
\catcode`@=11 % or \makeatletter to change category code of @ to 11 and temporarily to access kernel macro \@tabularcr
\let \clone@tabularcr \@tabularcr
\def\@tabularcr{\clone@tabularcr\hline}
\catcode`@=12 % or \makeatother to restore category code of @ to 12
\begin{document}
\begin{tabular}{lll}
Meaning & col2 & col3 \\
\meaning\\ & col2 & col3 \\
col1 & col2 & col3 \\
\end{tabular}
\end{document}

Array Package Changes

Note that the array package (and any package that loads array e.g. \RequirePackage{array}) changes the definition of \\ to

\relax \iffalse {\fi \ifnum 0=‘}\fi \@ifstar \@xarraycr \@xarraycr

\@tabularcr becomes \@xarraycr. So just replace \@tabularcr with \@xarraycr in the code above for compatibility with the array package.


Notes

In tabular environments, \\ is redefined in the LaTeX kernel at the beginning/end of the tabular environment.

Relevant Snippets from latex.ltx

See latex.ltx (LaTeX kernel) for details.

\def\@tabular{\leavevmode \hbox \bgroup $\let\@acol\@tabacol
   \let\@classz\@tabclassz
   \let\@classiv\@tabclassiv \let\\\@tabularcr\@tabarray}% sets \\ = \@tabularcr

% defines \@tabularcr to look for starred version
\def\@tabularcr{% 
  {\ifnum0=`}\fi\@ifstar\@xtabularcr\@xtabularcr}

% some starred/non-starred code for dealing with table rows
\def\@xtabularcr{\@ifnextchar[\@argtabularcr{\ifnum0=`{\fi}\cr}}
\def\@argtabularcr[#1]{%
  \ifnum0=`{\fi}%
    \ifdim #1>\z@
      \unskip\@xargarraycr{#1}%
    \else
      \@yargarraycr{#1}%
    \fi}
4
  • This doesn't seem to work with the array package. Is there another way to do this that does? Nov 14, 2016 at 11:33
  • @PeterFlynn See edit. Nov 14, 2016 at 12:22
  • The suggestion to "just replace \@tabularcr with \@xarraycr" will not be correct, that'll kill the optional argument of \\ inside of tabular, so \\[2cm] will print "[2cm]" in the output. Instead one needs to patch both \@xargarraycr and \@yargarraycr.
    – Skillmon
    Nov 27, 2022 at 18:47
  • Well, and \@xarraycr, but not in the way shown in this answer...
    – Skillmon
    Nov 28, 2022 at 14:41
7

The tabu package offer an \everyrow{\hline} command.

However be aware, that the tabu package is not yet supported if you want to go to html output via htlatex/tex4ht.

2
3

If you are used to vim and vim-latex you could add this to your ~/.vim/ftplugin/tex.vim

:call IMAP('ETA', "\\begin{table}[<+htbp+>]\<CR>\\centering\<CR>\\begin{tabular}{<+dimensions+>}\<CR>\\hline\<CR><++>\<CR>\\end{tabular}\<CR>\\caption{<+Caption text+>}\<CR>\\label{tab:<+label+>}\<CR>\\end{table}<++>", 'tex')
:call IMAP('LHL', "\\\\ \\hline\<CR>", 'tex')

Then, if you type ETA (like Environment TAble) while you are in the insertion mode, it will be automaticaly replaced by :

\begin{table}[<+htbp+>]
  \centering
  \begin{tabular}{<+dimensions+>}
    \hline
    <++>
  \end{tabular}
  \caption{<+Caption text+>}
  \label{tab:<+label+>}
\end{table}<++>

And if you type LHL (like Line HLine) it will automatically add \\ \hline at the end of the current line and begin a new line.

1
  • I'm not using vim... I would like a LaTeX solution, not a vim one
    – soandos
    May 1, 2013 at 6:27
2

A solution with tabularray package:

\documentclass[12pt]{article}

\usepackage{tabularray}

\begin{document}

\begin{tblr}{
  colspec = {lcr},
  hlines,
}
 a & b & c \\
 a & b & c \\
 a & b & c \\
 a & b & c \\
 a & b & c \\
 a & b & c \\
\end{tblr}

\end{document}

enter image description here

1

With the environment {NiceTabular} of nicematrix, you have a key hlines for that purpose.

\documentclass[12pt]{article}
\usepackage[english]{babel}
\usepackage{amsmath,amsthm}
\usepackage{amsfonts}
\usepackage{nicematrix}

\begin{document}
\begin{NiceTabular}{l}[hlines]
a\\
a\\
a\\
a\\
a\\
\end{NiceTabular}

\end{document}

You need several compilations (because nicematrix uses PGF/Tikz nodes under the hood).

Output of the above code

1

This code is similar to things I do in an unpublished package of mine that I used for several documents, most of this should therefore be relatively tested; though I never really used these nested. Still, use at your own risk.

Just from looking at the code, nesting harray in htabular and vice versa will use the wrong specific hooks in the nested environment, so there is at least one known bug for nesting.


The following provides two environments (htabular and harray) which are patches of the tabular and array environments. It adds a few hooks to them (and keeps track of the current line):

  • begin is used before the alignment starts
  • start is used before the first line inside the alignment (might contain \noalign)
  • bol is used at the begin of every line (in the first line after start, might contain \noalign))
  • eol is used at the end of every line (inside \\ such that it's part of the last used column)
  • lastline is used at the same place eol would've been used if the last line of the alignment wasn't ended with \\
  • stop is used after the last line (might contain \noalign)
  • end is used after the alignment ends

Each hook is provided three times:

  • common hooks
  • hooks only used inside htabular
  • hooks only used inside harray

The common hooks are always executed before the specific ones.

Hooks can be set and cleared via the macros:

  • \htabAddToHook[<specific>]{<hook>}{<code>} adds the <code> to the respective <hook>, if the optional argument is omitted the generic hook is used, else you can either specify tabular or array in it to set the specific one
  • \htabClearHook<*>[<specific>]{<hook>} clears the respective <hook>, if the optional star is given for each tabular, array and the generic ones, else if the optional argument is used for those specific one (either tabular or array) and if both is omitted the generic one
  • \htabClearAllHooks<*>[<specific>] clears all hooks of either tabular, array or the generic ones (or all if the star is given)

The row count can be accessed via htabrow, which is accessible via \value/\arabic/\roman/etc. (but is no real LaTeX-counter).

\documentclass{article}

\usepackage{array}
\usepackage{etoolbox}

\ExplSyntaxOn
\makeatletter
% row counter
\int_new:N \g_htab_row_int
\cs_new_eq:NN \c@htabrow \g_htab_row_int
\seq_new:N \g__htab_row_stack_seq
\cs_new_protected:Npn \__htab_push_row:% >>=
  {
    \seq_gpush:NV \g__htab_row_stack_seq \g_htab_row_int
    \int_gzero:N \g_htab_row_int
  }% =<<
\cs_new_protected:Npn \__htab_pop_row:% >>=
  {
    \group_begin:
      \seq_gpop:NN \g__htab_row_stack_seq \l_tmpa_tl
      \int_gset:Nn \g_htab_row_int \l_tmpa_tl
    \group_end:
  }% =<<
\seq_const_from_clist:Nn \c__htab_hook_names_seq% >>=
  { start, stop, bol, eol, begin, end, lastline }% =<<

% hooks
\seq_map_inline:Nn \c__htab_hook_names_seq % >>=
  {
    \clist_map_inline:nn { tabular, array }
      { \tl_new:c { l_htab_hook_ ##1/#1 _tl } }
    \tl_new:c { l_htab_hook_ #1 _tl }
  }% =<<
\cs_new:Npn \htab@hook@start@@% >>=
  {
    \l_htab_hook_start_tl
  }% =<<
\cs_new:Npn \htab@hook@stop@@% >>=
  {
    \l_htab_hook_stop_tl
  }% =<<
\cs_new:Npn \htab@hook@bol@@% >>=
  {
    \noalign { \int_gincr:N \g_htab_row_int }
    \l_htab_hook_bol_tl
  }% =<<
\cs_new:Npn \htab@hook@eol@@% >>=
  {
    \l_htab_hook_eol_tl
  }% =<<
\cs_new:Npn \htab@hook@lastline@@% >>=
  {
    \l_htab_hook_lastline_tl
  }% =<<
\cs_new:Npn \htab@hook@begin@@% >>=
  {
    \l_htab_hook_begin_tl
  }% =<<
\cs_new:Npn \htab@hook@end@@% >>=
  {
    \l_htab_hook_end_tl
  }% =<<
\cs_new_protected:Npn \__htab_add_hooks:n #1% >>=
  {
    \seq_map_inline:Nn \c__htab_hook_names_seq
      { \tl_put_right:cv { l_htab_hook_ ##1 _tl } { l_htab_hook_ #1/##1 _tl } }
  }% =<<
\cs_generate_variant:Nn \tl_put_right:Nn { cv }

% patches
\msg_new:nnn { htab } { patch~failed } { Patching~ of~ #1 failed. }
\msg_new:nnn { htab } { unknown~hook } { Unknown~ hook~ #1. }
\cs_new_protected:Npn \__htab_pretocmd:Nn #1 #2% >>=
  {
    \pretocmd #1 {#2} {} { \msg_error:nnn { htab } { patch~failed } {#1} }
  }% =<<
\cs_new_protected:Npn \__htab_apptocmd:Nn #1 #2% >>=
  {
    \apptocmd #1 {#2} {} { \msg_error:nnn { htab } { patch~failed } {#1} }
  }% =<<
\cs_new_protected:Npn \__htab_patchcmd:Nnn #1 #2 #3% >>=
  {
    \patchcmd #1 {#2} {#3} {} { \msg_error:nnn { htab } { patch~failed} {#1} }
  }% =<<
\cs_new_protected:Npn \__htab_backup:% >>=
  {
    \cs_set_eq:NN \__htab_unpatched_@@array:      \@@array
    \cs_set_eq:NN \__htab_unpatched_@arraycr:     \@arraycr
    \cs_set_eq:NN \__htab_unpatched_@xarraycr:    \@xarraycr
    \cs_set_eq:NN \__htab_unpatched_@xargarraycr: \@xargarraycr
    \cs_set_eq:NN \__htab_unpatched_@yargarraycr: \@yargarraycr
    \cs_set_eq:NN \__htab_unpatched_tabular:      \tabular
    \cs_set_eq:NN \__htab_unpatched_endtabular:   \endtabular
    \cs_set_eq:NN \__htab_unpatched_array:        \array
    \cs_set_eq:NN \__htab_unpatched_endarray:     \endarray
  }% =<<
\cs_new_protected:Npn \htab@unpatch@@% >>=
  {
    \cs_set_eq:NN \@@array      \__htab_unpatched_@@array:
    \cs_set_eq:NN \@arraycr     \__htab_unpatched_@arraycr:
    \cs_set_eq:NN \@xarraycr    \__htab_unpatched_@xarraycr:
    \cs_set_eq:NN \@xargarraycr \__htab_unpatched_@xargarraycr:
    \cs_set_eq:NN \@yargarraycr \__htab_unpatched_@yargarraycr:
    \cs_set_eq:NN \tabular      \__htab_unpatched_tabular:
    \cs_set_eq:NN \endtabular   \__htab_unpatched_endtabular:
    \cs_set_eq:NN \array        \__htab_unpatched_array:
    \cs_set_eq:NN \endarray     \__htab_unpatched_endarray:
  }% =<<
% due to etoolbox restrictions names of macros that are patched in don't follow
% the expl3 naming conventions
\cs_new_protected:Npn \__htab_patch:n #1% >>=
  {
    \bool_lazy_or:nnT
      { \cs_if_eq_p:NN \__htab_unpatched_tabular: \tabular }
      { !\cs_if_exist_p:N \__htab_unpatched_tabular: }
      {
        \__htab_backup:
        \__htab_pretocmd:Nn  \tabular      \htab@unpatch@@
        \__htab_pretocmd:Nn  \array        \htab@unpatch@@
        \__htab_pretocmd:Nn  \@arraycr     \htab@hook@eol@@
        \__htab_apptocmd:Nn  \@yargarraycr \htab@hook@bol@@
        \__htab_apptocmd:Nn  \@xargarraycr \htab@hook@bol@@
        \__htab_patchcmd:Nnn \@xarraycr    \cr   { \cr \htab@hook@bol@@ }
        \__htab_patchcmd:Nnn \endarray     \crcr { \crcr \htab@hook@stop@@ }
        \__htab_patchcmd:Nnn \@@array      \cr
          { \cr \noexpand \htab@hook@start@@ \noexpand \htab@hook@bol@@ }
        \__htab_add_hooks:n {#1}
      }
  }% =<<

\cs_new_protected:Npn \htab_add_to_hook:nnn #1#2#3% >>=
  {
    \tl_if_exist:cTF { l_htab_hook_ #1#2 _tl }
      { \tl_put_right:cn { l_htab_hook_ #1#2 _tl } {#3} }
      { \msg_error:nnn { htab } { unknown~hook } {#1#2} }
  }% =<<
\NewDocumentCommand \htabAddToHook { o m m }% >>=
  {
    \tl_if_novalue:nTF {#1}
      { \htab_add_to_hook:nnn {} }
      { \htab_add_to_hook:nnn { #1/ } }
        {#2} {#3}
  }% =<<
\cs_new_protected:Npn \htab_clear_hook:n #1% >>=
  { \tl_clear:c { l_htab_hook_ #1 _tl } }% =<<
\NewDocumentCommand \htabClearHook { s o m }% >>=
  {
    \IfBooleanTF {#1}
      {
        \htab_clear_hook:n { tabular/#3 }
        \htab_clear_hook:n { array/#3 }
        \htab_clear_hook:n {#3}
      }
      {
        \tl_if_novalue:nTF {#2}
          { \htab_clear_hook:n {#3} }
          { \htab_clear_hook:n { #2/#3 } }
      }
  }% =<<
\cs_new_protected:Npn \htab_clear_all_hooks:n #1% >>=
  {
    \seq_map_inline:Nn \c__htab_hook_names_seq
      { \htab_clear_hook:n { #1##1 } }
  }% =<<
\NewDocumentCommand \htabClearAllHooks { s o }% >>=
  {
    \IfBooleanTF {#1}
      {
        \htab_clear_all_hooks:n { tabular/ }
        \htab_clear_all_hooks:n { array/ }
        \htab_clear_all_hooks:n {}
      }
      {
        \tl_if_novalue:nTF {#2}
          { \htab_clear_all_hooks:n {} }
          { \htab_clear_all_hooks:n { #2/ } }
      }
  }% =<<

\NewDocumentEnvironment { htabular } {}% >>=
  {
    \__htab_push_row:
    \__htab_patch:n { tabular }
    \htab@hook@begin@@
    \__htab_unpatched_tabular:
  }
  {
    \ifvmode\else\expandafter\htab@hook@lastline@@\fi
    \endtabular
    \htab@hook@end@@
    \__htab_pop_row:
  }% =<<
\NewDocumentEnvironment { harray } {}% >>=
  {
    \__htab_push_row:
    \__htab_patch:n { array }
    \__htab_unpatched_array:
  }
  {
    \ifvmode\else\expandafter\htab@hook@eol@@\fi
    \endarray
    \__htab_pop_row:
  }% =<<
\makeatother
\ExplSyntaxOff

% this might be a good idea, this way a `htabular` and `harray` always behave as
% if the last line was ended with `\\` (so should give consistent results)
\htabAddToHook{lastline}{\\}

\usepackage{booktabs}% just for an example

\newcommand\smartmidrule[1] % maybe this isn't really smart :P
  {%
    \ifnum\value{htabrow}=\numexpr#1\relax
      \midrule
    \fi
  }

\newcolumntype\pseudoenum{@{\hskip\tabcolsep\arabic{htabrow}.\hskip2\tabcolsep}}

\begin{document}
\noindent
Putting \verb|\hline| in \texttt{bol}:\par
\htabAddToHook[tabular]{bol}{\hline}
\begin{htabular}{ll}
  a & b \\
  c & d % would have no \hline without the \htabAddToHook{lastline}{\\}
\end{htabular}

\noindent
array is not affected since \texttt{[tabular]} was used:\par
$\begin{harray}{ll}
  a & b \\
  c & d
\end{harray}$

\bigskip
\noindent
Autostyling with \texttt{booktabs} macros and putting \texttt{end} in
\texttt{eol}:\par
\htabClearHook*{bol}
\htabAddToHook{start}{\toprule}
\htabAddToHook{bol}{\smartmidrule{2}}
\htabAddToHook{stop}{\bottomrule}
\htabAddToHook{eol}{\unskip end}
\begin{htabular}{ll}
  my & headrow \\
  a & b \\
  c & d \\
\end{htabular}

\bigskip
\noindent
Nested \texttt{tabular}s aren't affected:\par
\htabClearHook{eol}
\begin{htabular}{ll}
  my & headrow \\
  \begin{tabular}{|c|}a\\A\end{tabular} & b \\
  c & d
\end{htabular}

\bigskip
\noindent
Nested \texttt{htabular}s don't break the row count (but are affected):\par
\htabClearAllHooks*
\htabAddToHook[tabular]{start}{\hline}
\htabAddToHook[tabular]{stop}{\hline}
\begin{htabular}{\pseudoenum ll}
  Some & tabular \\
  points & \begin{htabular}{\pseudoenum c@{}}making\\absolutely\end{htabular} \\
  no & sense
\end{htabular}
\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .