PDFPipe

People and payroll / A4 landscape

Weekly timesheet PDF template

Hours by project and by day across a week, totalled both ways, with an approval signature.

When this document is the right one

Time is recorded for payroll, for client billing, or both. The document is submitted weekly and approved by someone who was not there, so it has to be checkable rather than merely complete.

Why each part is there

The fields are visible in the markup. What is not visible is why they are present, which is the part that gets removed first when someone adapts a template for their own use.

  • Seven day columns and a row per project and task, so the grid totals in both directions and an error in either shows up.
  • Weekend columns tinted, because unauthorised weekend hours are the thing an approver is looking for.
  • Absence recorded on the same sheet as work, since a week with a day of leave should still total to a full week.
  • A summary strip separating standard hours, overtime, absence and billable, which are four different downstream consumers of the same grid.
  • A declaration above the signature, because that sentence is what makes the signature mean anything.

The template

A complete file. Doctype, stylesheet, body: paste it into a renderer unchanged and it produces a finished A4 landscape page. Replace the sample data and the styling holds.

html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Timesheet, week ending 17 May 2026</title>
<style>
  /* Landscape, because a week has seven columns and a portrait page squeezes
     the project name into two characters. */
  @page { size: A4 landscape; margin: 12mm; }
  * { box-sizing: border-box; }
  body { margin: 0; font: 9pt/1.35 Arial, sans-serif; color: #111; }

  header { display: flex; justify-content: space-between; align-items: flex-end;
           border-bottom: 2px solid #111; padding-bottom: 4mm; }
  h1 { margin: 0; font-size: 15pt; }
  .who { display: grid; grid-template-columns: auto auto; gap: 1mm 4mm; font-size: 9pt; }
  .who dt { color: #666; }
  .who dd { margin: 0; }

  table { width: 100%; border-collapse: collapse; margin-top: 5mm; }
  thead { display: table-header-group; }
  th, td { border: 1px solid #bbb; padding: 2mm; }
  th { background: #f2f2f2; font-size: 7.5pt; text-transform: uppercase;
       letter-spacing: 0.05em; text-align: center; }
  th:first-child, th:nth-child(2) { text-align: left; }
  tr { break-inside: avoid; }
  td.day { text-align: center; font-variant-numeric: tabular-nums; }
  /* Weekend columns tinted so an entry there is noticed at approval rather
     than at payroll. */
  .weekend { background: #f7f4ee; }
  tfoot td { font-weight: 700; background: #f2f2f2; }

  .totals-strip { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4mm;
                  margin-top: 5mm; break-inside: avoid; }
  .totals-strip div { border: 1px solid #bbb; padding: 3mm; text-align: center; }
  .totals-strip .k { font-size: 7.5pt; text-transform: uppercase; color: #666;
                     letter-spacing: 0.05em; }
  .totals-strip .v { font-size: 14pt; font-weight: 700; font-variant-numeric: tabular-nums;
                     margin-top: 1mm; }

  .declaration { margin-top: 6mm; display: grid; grid-template-columns: 1fr 60mm 60mm;
                 gap: 8mm; align-items: end; break-inside: avoid; font-size: 8.5pt; }
  .declaration .rule { border-top: 1px solid #111; padding-top: 1.5mm; margin-top: 12mm;
                       color: #666; }
</style>
</head>
<body>
  <header>
    <div>
      <h1>Weekly timesheet</h1>
      <div style="margin-top:1.5mm">Kilnworks Studio Ltd</div>
    </div>
    <dl class="who">
      <dt>Employee</dt><dd>S. Whitlock</dd>
      <dt>Payroll number</dt><dd>KW-0142</dd>
      <dt>Week ending</dt><dd>Sunday 17 May 2026</dd>
      <dt>Approver</dt><dd>E. Prentice</dd>
    </dl>
  </header>

  <table>
    <thead>
      <tr>
        <th>Project</th><th>Task</th>
        <th>Mon 11</th><th>Tue 12</th><th>Wed 13</th><th>Thu 14</th><th>Fri 15</th>
        <th class="weekend">Sat 16</th><th class="weekend">Sun 17</th>
        <th>Total</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>RVH-2026</td><td>Ravenshill range, throwing</td>
        <td class="day">7.5</td><td class="day">7.5</td><td class="day">4.0</td>
        <td class="day">-</td><td class="day">-</td>
        <td class="day weekend">-</td><td class="day weekend">-</td>
        <td class="day">19.0</td>
      </tr>
      <tr>
        <td>RVH-2026</td><td>Glaze trials, kiln 2</td>
        <td class="day">-</td><td class="day">-</td><td class="day">3.5</td>
        <td class="day">7.5</td><td class="day">3.0</td>
        <td class="day weekend">-</td><td class="day weekend">-</td>
        <td class="day">14.0</td>
      </tr>
      <tr>
        <td>INT-STUDIO</td><td>Kiln maintenance</td>
        <td class="day">-</td><td class="day">-</td><td class="day">-</td>
        <td class="day">-</td><td class="day">2.0</td>
        <td class="day weekend">4.5</td><td class="day weekend">-</td>
        <td class="day">6.5</td>
      </tr>
      <tr>
        <td>ABSENCE</td><td>Annual leave</td>
        <td class="day">-</td><td class="day">-</td><td class="day">-</td>
        <td class="day">-</td><td class="day">2.5</td>
        <td class="day weekend">-</td><td class="day weekend">-</td>
        <td class="day">2.5</td>
      </tr>
    </tbody>
    <tfoot>
      <tr>
        <td colspan="2">Daily total</td>
        <td class="day">7.5</td><td class="day">7.5</td><td class="day">7.5</td>
        <td class="day">7.5</td><td class="day">7.5</td>
        <td class="day weekend">4.5</td><td class="day weekend">0.0</td>
        <td class="day">42.0</td>
      </tr>
    </tfoot>
  </table>

  <div class="totals-strip">
    <div><div class="k">Standard hours</div><div class="v">35.0</div></div>
    <div><div class="k">Overtime 1.5x</div><div class="v">4.5</div></div>
    <div><div class="k">Annual leave</div><div class="v">2.5</div></div>
    <div><div class="k">Sick</div><div class="v">0.0</div></div>
    <div><div class="k">Billable</div><div class="v">33.0</div></div>
  </div>

  <div class="declaration">
    <div>
      I confirm that the hours recorded above are a true record of time worked and
      that any weekend or overtime hours were authorised in advance.
    </div>
    <div class="rule">Employee signature and date</div>
    <div class="rule">Approved by and date</div>
  </div>
</body>
</html>

The layout decision worth understanding

Landscape A4. A week has seven columns plus a project name, a task name and a total, and in portrait the project name is squeezed to two characters. Choosing landscape is the difference between a usable grid and an abbreviation exercise.

The mistake people make adapting it

Totalling only across rows and not down columns. A day that should be seven and a half hours and comes to nine and a half is only visible in the column total, and that is the error worth catching before it reaches payroll.

Rendering it

The page box is declared in the template's own CSS as A4, so the size travels with the markup rather than living in the code that calls the renderer. A4 is also one of the six named formats the API accepts, so it can be passed in the request options instead if that suits your setup better.

bash
curl -X POST https://api.pdfpipe.xyz/v1/pdf \
  -H "Authorization: Bearer $PDFPIPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<!doctype html>...",
    "options": { "format": "A4" }
  }' \
  --output timesheet.pdf

Frequently asked

Can I use this template commercially?

Yes. Copy it, change it, ship it. It is sample markup written to be taken, and the sample data in it is invented, so replace the names and the numbers before anyone sees it.

Why is the page size in the CSS rather than in the API call?

Because a weekly timesheet is a A4 landscape document, and that is a property of the document rather than of the code that renders it. Declaring it in the page box means the size cannot be lost between the template and the call, which is the usual way a document ends up on the wrong paper.

Will it look the same in every renderer?

The layout will, because it uses ordinary CSS. The page break behaviour will not: break-inside and break-after are implemented differently by browser engines and by dedicated print engines, and some of the paged media specification is not implemented in browser renders at all. That difference is documented per property in the paged CSS reference.

Other templates

Documents from the same part of a business, and one from each of the other groups.

Paste the template into the playground and get the PDF back. No signup, no key.