Skip to content

PDF Template Components - Reusable Blocks

Component library for building professional PDF templates with reusable blocks.


Overview

PDF template components are reusable HTML/CSS snippets that can be inserted into multiple templates. They ensure:

  • Consistency - Same letterhead across all documents
  • Maintainability - Update once, applies to all templates
  • Organization branding - Each org has custom components
  • Time savings - Drag-drop instead of writing HTML

Component Categories

CategoryPurposeExamples
letterheadOrganization headerLogo + name + address
footerPage footerOrganization info, page numbers, legal notice
signatureSpecialist signatureSignature image, name, title, license
headerDocument headerDocument title, metadata, date
tableReusable table layoutsService line items, medication tables
sectionContent sectionsPatient info, findings, recommendations

Default Components

1. Standard Letterhead

Category: letterhead

HTML:

html
<div class="letterhead">
  <div class="letterhead-logo">
    {{if .Organization.LogoURL}}
      <img src="{{.Organization.LogoURL}}" alt="{{.Organization.Name}}" class="logo" />
    {{end}}
  </div>
  <div class="letterhead-info">
    <h1 class="org-name">{{.Organization.Name}}</h1>
    <p class="org-contact">
      {{.Organization.Address}}<br>
      Tel: {{.Organization.Phone}} | Email: {{.Organization.Email}}
      {{if .Organization.Website}}
        <br>{{.Organization.Website}}
      {{end}}
    </p>
  </div>
</div>

CSS:

css
.letterhead {
  text-align: center;
  border-bottom: 2px solid #003366;
  padding-bottom: 10mm;
  margin-bottom: 10mm;
}

.letterhead-logo {
  margin-bottom: 5mm;
}

.letterhead .logo {
  max-width: 150px;
  height: auto;
}

.letterhead .org-name {
  font-size: 18pt;
  color: #003366;
  margin: 0 0 5px 0;
  font-weight: bold;
}

.letterhead .org-contact {
  font-size: 10pt;
  color: #666;
  margin: 0;
  line-height: 1.4;
}

Variables Used:

  • Organization.LogoURL
  • Organization.Name
  • Organization.Address
  • Organization.Phone
  • Organization.Email
  • Organization.Website

2. Specialist Signature Block

Category: signature

HTML:

html
<div class="signature-block">
  <div class="signature-line">
    {{if .Specialist.SignatureURL}}
      <img src="{{.Specialist.SignatureURL}}" alt="Signature" class="signature-image" />
    {{else}}
      <div class="signature-placeholder">
        _______________________________
      </div>
    {{end}}
  </div>
  <div class="signature-info">
    <p class="specialist-name"><strong>{{.Specialist.Name}}</strong></p>
    <p class="specialist-title">{{.Specialist.Title}}</p>
    {{if .Specialist.LicenseNumber}}
      <p class="specialist-license">License No: {{.Specialist.LicenseNumber}}</p>
    {{end}}
  </div>
</div>

CSS:

css
.signature-block {
  margin-top: 30mm;
  text-align: right;
  page-break-inside: avoid;
}

.signature-line {
  margin-bottom: 5mm;
}

.signature-image {
  max-width: 200px;
  height: auto;
}

.signature-placeholder {
  font-size: 12pt;
  color: #999;
}

.signature-info {
  font-size: 11pt;
  line-height: 1.4;
}

.signature-info p {
  margin: 2px 0;
}

.specialist-name {
  font-size: 12pt;
}

.specialist-title {
  color: #666;
  font-style: italic;
}

.specialist-license {
  font-size: 10pt;
  color: #666;
}

Variables Used:

  • Specialist.SignatureURL
  • Specialist.Name
  • Specialist.Title
  • Specialist.LicenseNumber

Category: footer

HTML:

html
<div class="page-footer">
  <div class="footer-content">
    <p class="footer-text">
      {{.Organization.Name}} | Confidential Medical Document
      {{if .GeneratedAt}}
        | Generated: {{.GeneratedAt.Format "2006-01-02 15:04"}}
      {{end}}
    </p>
    <p class="footer-legal">
      This document contains confidential medical information. Unauthorized disclosure is prohibited.
    </p>
  </div>
</div>

CSS:

css
.page-footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 9pt;
  color: #666;
  border-top: 1px solid #ccc;
  padding-top: 3mm;
  background: white;
}

.footer-content {
  max-width: 170mm;
  margin: 0 auto;
}

.footer-text {
  margin: 0 0 2mm 0;
  font-size: 9pt;
}

.footer-legal {
  margin: 0;
  font-size: 8pt;
  color: #999;
  font-style: italic;
}

Variables Used:

  • Organization.Name
  • GeneratedAt

4. Patient Information Section

Category: section

HTML:

html
<div class="patient-info-section">
  <h3 class="section-title">Patient Information</h3>
  <div class="info-grid">
    <div class="info-row">
      <span class="info-label">Name:</span>
      <span class="info-value">{{.Patient.Name}}</span>
    </div>
    <div class="info-row">
      <span class="info-label">Date of Birth:</span>
      <span class="info-value">{{.Patient.Birthdate.Format "January 2, 2006"}}</span>
    </div>
    <div class="info-row">
      <span class="info-label">Age:</span>
      <span class="info-value">{{.Patient.Age}} years</span>
    </div>
    {{if .Patient.Phone}}
    <div class="info-row">
      <span class="info-label">Phone:</span>
      <span class="info-value">{{.Patient.Phone}}</span>
    </div>
    {{end}}
    {{if .Patient.Email}}
    <div class="info-row">
      <span class="info-label">Email:</span>
      <span class="info-value">{{.Patient.Email}}</span>
    </div>
    {{end}}
  </div>
</div>

CSS:

css
.patient-info-section {
  margin-bottom: 10mm;
  page-break-inside: avoid;
}

.section-title {
  font-size: 14pt;
  color: #003366;
  border-bottom: 1px solid #003366;
  padding-bottom: 2mm;
  margin-bottom: 5mm;
}

.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3mm;
}

.info-row {
  display: flex;
}

.info-label {
  font-weight: bold;
  min-width: 120px;
  flex-shrink: 0;
}

.info-value {
  flex: 1;
}

Variables Used:

  • Patient.Name
  • Patient.Birthdate
  • Patient.Age
  • Patient.Phone
  • Patient.Email

5. Form Fields List

Category: section

HTML:

html
<div class="form-fields-section">
  <h3 class="section-title">Consultation Details</h3>
  <div class="fields-list">
    {{range .FormFields}}
      {{if not .IsPrivate}}
        <div class="field-row">
          <span class="field-label">{{.Label}}:</span>
          <span class="field-value">{{.Value}}</span>
        </div>
      {{end}}
    {{end}}
  </div>
</div>

CSS:

css
.form-fields-section {
  margin-bottom: 10mm;
}

.fields-list {
  background: #f9f9f9;
  padding: 5mm;
  border-radius: 2mm;
}

.field-row {
  display: flex;
  padding: 2mm 0;
  border-bottom: 1px solid #eee;
}

.field-row:last-child {
  border-bottom: none;
}

.field-label {
  font-weight: bold;
  min-width: 150px;
  flex-shrink: 0;
  color: #003366;
}

.field-value {
  flex: 1;
}

Variables Used:

  • FormFields (array)
    • FormFields[].Label
    • FormFields[].Value
    • FormFields[].IsPrivate

6. Appointment Details Header

Category: header

HTML:

html
<div class="appointment-header">
  <div class="header-left">
    <h2 class="document-title">Consultation Report</h2>
    <p class="document-subtitle">{{.Appointment.ServiceName}}</p>
  </div>
  <div class="header-right">
    <p class="appointment-date">
      <strong>Date:</strong> {{.Appointment.StartedAt.Format "January 2, 2006"}}
    </p>
    <p class="appointment-time">
      <strong>Time:</strong> {{.Appointment.StartedAt.Format "15:04"}} - {{.Appointment.EndedAt.Format "15:04"}}
    </p>
  </div>
</div>

CSS:

css
.appointment-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 10mm;
  padding-bottom: 5mm;
  border-bottom: 2px solid #003366;
}

.header-left {
  flex: 1;
}

.document-title {
  font-size: 20pt;
  color: #003366;
  margin: 0 0 3mm 0;
}

.document-subtitle {
  font-size: 12pt;
  color: #666;
  margin: 0;
}

.header-right {
  text-align: right;
  font-size: 11pt;
}

.header-right p {
  margin: 2mm 0;
}

Variables Used:

  • Appointment.ServiceName
  • Appointment.StartedAt
  • Appointment.EndedAt

7. Medication Table

Category: table

HTML:

html
<div class="medication-table">
  <h3 class="section-title">Prescribed Medications</h3>
  <table class="data-table">
    <thead>
      <tr>
        <th>Medication</th>
        <th>Dosage</th>
        <th>Frequency</th>
        <th>Duration</th>
      </tr>
    </thead>
    <tbody>
      {{range .Medications}}
      <tr>
        <td>{{.Name}}</td>
        <td>{{.Dosage}}</td>
        <td>{{.Frequency}}</td>
        <td>{{.Duration}}</td>
      </tr>
      {{end}}
    </tbody>
  </table>
</div>

CSS:

css
.medication-table {
  margin-bottom: 10mm;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11pt;
}

.data-table thead {
  background: #003366;
  color: white;
}

.data-table th {
  padding: 3mm;
  text-align: left;
  font-weight: bold;
}

.data-table td {
  padding: 3mm;
  border-bottom: 1px solid #ddd;
}

.data-table tbody tr:nth-child(even) {
  background: #f9f9f9;
}

.data-table tbody tr:hover {
  background: #f0f0f0;
}

Variables Used:

  • Medications (array)
    • Medications[].Name
    • Medications[].Dosage
    • Medications[].Frequency
    • Medications[].Duration

Creating Custom Components

  1. Navigate to Settings → PDF Templates → Components
  2. Click "Create Component"
  3. Fill in:
    • Name: "Custom Letterhead"
    • Category: letterhead
    • Description: "Letterhead with custom branding"
  4. Write HTML in editor (Monaco editor with syntax highlighting)
  5. Write CSS in editor
  6. Click "Preview" to see rendered component
  7. Click "Save"

Via API

bash
curl -X POST https://api.example.com/v1/pdf-template-components \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Custom Letterhead",
    "description": "Letterhead with custom branding",
    "category": "letterhead",
    "component_html": "<div class=\"letterhead\">...</div>",
    "component_css": ".letterhead { text-align: center; }",
    "variables_used": ["Organization.LogoURL", "Organization.Name"]
  }'

Using Components in Templates

Via Block Editor

  1. Drag "Letterhead" block from palette
  2. In properties panel:
    • Select "Standard Letterhead" from dropdown
    • Or click "Customize" to edit inline
  3. Component HTML/CSS is inserted into template

Manually in HTML

html
<!DOCTYPE html>
<html>
<head>
  <style>
    {{template "letterhead_css"}}
  </style>
</head>
<body>
  {{template "letterhead"}}

  <h2>Consultation Report</h2>

  <!-- Your content here -->

  {{template "signature"}}
  {{template "footer"}}
</body>
</html>

Component Versioning

Components are not versioned (unlike templates). Changes to components affect all templates that use them immediately.

Best practices:

  1. Don't break existing templates - If making breaking changes, create a new component
  2. Test before updating - Preview all templates using the component
  3. Communicate changes - Notify admins when updating shared components

Component Library Management

Searching Components

typescript
const SearchComponents: React.FC = () => {
  const [components, setComponents] = useState<PDFTemplateComponent[]>([]);
  const [searchTerm, setSearchTerm] = useState('');
  const [categoryFilter, setCategoryFilter] = useState<string | null>(null);

  const filteredComponents = useMemo(() => {
    return components.filter(c => {
      const matchesSearch = c.name.toLowerCase().includes(searchTerm.toLowerCase());
      const matchesCategory = !categoryFilter || c.category === categoryFilter;
      return matchesSearch && matchesCategory;
    });
  }, [components, searchTerm, categoryFilter]);

  return (
    <div className="component-library">
      <SearchBar value={searchTerm} onChange={setSearchTerm} />
      <CategoryFilter value={categoryFilter} onChange={setCategoryFilter} />

      <div className="component-grid">
        {filteredComponents.map(component => (
          <ComponentCard
            key={component.id}
            component={component}
            onInsert={() => insertComponent(component)}
          />
        ))}
      </div>
    </div>
  );
};

Component Preview

typescript
const ComponentPreview: React.FC<{ component: PDFTemplateComponent }> = ({ component }) => {
  const [previewHTML, setPreviewHTML] = useState('');

  useEffect(() => {
    // Merge component with sample data
    const sampleData = {
      Organization: {
        Name: 'Sample Clinic',
        LogoURL: 'https://via.placeholder.com/150',
        Address: '123 Main St',
        Phone: '+1 234 567 8900',
      },
    };

    // Render component HTML with sample data
    const html = renderTemplate(component.component_html, sampleData);

    // Wrap in HTML document with CSS
    const fullHTML = `
      <!DOCTYPE html>
      <html>
      <head>
        <style>${component.component_css}</style>
      </head>
      <body>
        ${html}
      </body>
      </html>
    `;

    setPreviewHTML(fullHTML);
  }, [component]);

  return (
    <div className="component-preview">
      <iframe srcDoc={previewHTML} sandbox="allow-same-origin" />
    </div>
  );
};

Advanced: Nested Components

Components can reference other components:

html
<!-- "Full Report Header" component -->
<div class="report-header">
  {{template "letterhead"}}

  <div class="appointment-details">
    {{template "appointment_header"}}
  </div>
</div>

Implementation:

  1. Store component dependencies in components_used JSONB
  2. When rendering, resolve nested components recursively
  3. Prevent circular dependencies (max depth: 3)

Component Analytics

Track component usage to understand which are most popular:

sql
-- Find most-used components
SELECT
  c.id,
  c.name,
  c.category,
  COUNT(DISTINCT t.id) AS templates_using
FROM pdf_template_components c
LEFT JOIN pdf_templates t ON t.components_used @> jsonb_build_array(c.name)
GROUP BY c.id, c.name, c.category
ORDER BY templates_using DESC;

Component Marketplace (Future)

Allow sharing components across organizations:

sql
ALTER TABLE pdf_template_components
ADD COLUMN is_public BOOLEAN DEFAULT FALSE,
ADD COLUMN downloads INT DEFAULT 0;

-- Public components (shared across orgs)
SELECT * FROM pdf_template_components
WHERE is_public = true
ORDER BY downloads DESC;

Summary

7 default components - Letterhead, footer, signature, patient info, form fields, appointment header, medication table ✅ Organization-scoped - Each org can customize components ✅ Reusable - Insert into multiple templates ✅ Category-based - Organized by purpose (header, footer, signature, etc.) ✅ Live preview - See component before inserting ✅ Template variables - Access all template data (Organization, Patient, Specialist, etc.) ✅ No versioning - Changes affect all templates immediately

Components are the building blocks of professional PDF templates, ensuring consistency and saving time.