Edit online

The page headers and footers use the string sets defined for publication, chapter, and section titles. These string-sets are defined in the numbering CSS:
parttitle
Set to the title of the current part (only for DITA bookmaps that use parts).
chaptertitle
Set to the title of the current chapter (Shallow and Deep numbering).
sectiontitle
Set to the title of each section (Deep numbering only).

Although you may define string sets in your customization CSS, you need to take into account the fact that the string-set CSS property is not additive, and matching the same elements will end up breaking the current definitions. A very common use-case is to match the title element that is also used in the default CSS. The best approach, in this case, is to take a look at the rules from the numbering CSS, copy the ones dealing with string sets to your customization, then alter the property definition by adding your definition to the existing ones (and not removing the existing ones).

Edit online

The headers and footers are part of the page definitions. To see how the default page layouts are defined, see: Default Page Definitions.

Edit online

By default, the name of the publication and chapter titles are placed in the top-left or top-right page margin boxes:

   @page :left {
        @top-left {
            content: string(maptitle) string(parttitle) string(chaptertitle) string(sectiontitle) " | " counter(page);
        }
    }

    @page :right{
        @top-right {
            content: string(maptitle) string(parttitle) string(chaptertitle) string(sectiontitle) " | " counter(page);
        }
    }

If you want to change this, you should use the content CSS properties of other page margin boxes, and inhibit the ones in the above content. For example, to set the chapter title in the page top left corner, you can use:

   @page :left {
        @top-left {
            content:none;
        }
        @top-left-corner {
            content: string(maptitle) string(parttitle) string(chaptertitle) string(sectiontitle) " | " counter(page);
            white-space: nowrap;	
            text-align:left;
        }
    }

    @page :right{
        @top-right {
            content: none;
        }
        @top-right-corner {
            content: string(maptitle) string(parttitle) string(chaptertitle) string(sectiontitle) " | " counter(page);
            white-space: nowrap;	
            text-align:right;
        }
    }
Note: The corner page margin boxes are fixed and limited as the available space. Above, the text-align and white-space properties are used to make the text bleed out of these boxes towards the center of the page. If you plan to add an image or artwork background, you should consider using the technique described in: How to Decorate the Header by Using a Background Image on the Entire Page.

Edit online

This is directly related to the page margins and size.

The headers and footers are placed in the so-called page margin boxes, a series of rectangular areas residing in the page margins.

To affect the margins of all page definitions, you may use the following rule:

@page {
	margin-top:3cm !important;
	margin-bottom:3cm !important;
	margin-left:2cm !important;
	margin-right:2cm !important;	
}

If you want to affect only a specific page, like the first page from chapters for instance, you must use more specific page selectors. See the Default Page Definitions for details.

Note that the page margin boxes fill the entire page margin. This means the margin-top, for example, dictates the height of the @top-left-corner, @top-left, @top-center, @top-right, @top-right-corner margin boxes. These cannot have margins on themselves, so to change the position of the content inside them, you must use padding properties:

@page {
	@top-left {
		content:"..."
		padding: 1cm;
	}
	..
}

Edit online

To change the font for all the headers and footers, in your customization CSS, add a CSS rule similar to this:

@page {
    font-size: 12pt;
    font-family: "Arial";
}
Important: These settings apply to all page margin boxes, but not to the text inside the page.

If you want to change the settings only for a specific page type (for example, the table of contents), use the name of the page:

@page table-of-contents {
    font-size: 12pt;
    font-family: "Arial";
}

Edit online

It is possible to dynamically change the header depending on the content in a topic. The following example assumes that the data to be presented in the header is located in the metadata section of each topic. One way is to specify it in the DITA map is by using the <topicmeta> element for the <topicref> topic reference:

...
 <topicref href="topics/installing.dita">
    <topicmeta>
      <data name="header-data" value="ID778-3211"/>
    </topicmeta>
    ...

In the above example, there is set of key value pairs with the name header-data. This information is automatically copied into the content in the merged map file, like this:

<topic ... >
    <title class="- topic/title ">Installing</title>
    <shortdesc class="- topic/shortdesc ">You install components to make them available for your
      solution.</shortdesc>
    <prolog class="- topic/prolog ">
      ...
      <data class="- topic/data " name="header-data" value="ID778-3211"/>
      ...

This information can be extracted from the CSS:

/* Define the string set variable that contains the text extracted from the data element */
*[class ~= "topic/topic"] *[class ~= "topic/data"][name="header-data"] {
  string-set: hdrstr attr(value);
}

/* Using the value='none' stops applying the image. */
*[class ~= "topic/topic"] *[class ~= "topic/data"][name="header-data"][value="none"] {
  string-set: hdrstr "";
}

/* Use the string set variable in one of the page margin boxes. */
@page chapter {  
  @top-left-corner {
    content: string(hdrstr);
  }
}
Notes: The string set is applied to all pages that follow the data element, until another data element changes it:
...
 <topicref href="topics/installing.dita">
    <topicmeta>
      <data name="header-data" value="ID778-3211"/>
    </topicmeta>
 </topicref>
 <topicref href="..."> <!-- Uses the same value -->
 <topicref href="..."> <!-- Uses the same value -->
 <topicref href="..."> <!-- Uses the same value -->
 <topicref href="topics/change.dita">
    <topicmeta>
      <data name="header-data" value="ID990-3200"/>
    </topicmeta>
 </topicref>
 <topicref href="..."> <!-- The string set is changed now -->
 <topicref href="..."> <!-- The string set is changed now -->
 <topicref href="..."> <!-- The string set is changed now -->
To clear the text, use the none value:
...
     <topicref href="..."> <!-- The string set is void now -->

...

Edit online

It is possible to dynamically change an image in the header depending on the chapter. For this, you need to define an image reference in the metadata section of each chapter. One way is to specify it in the DITA map by using the <topicmeta> element for the <chapter> topic reference:

...
 <chapter href="topics/installing.dita">
    <topicmeta>
      <data name="header-image" value="img/installing.png"/>
    </topicmeta>
    ...

In the above example, there is set of key value pairs with the name header-image. The img/installing.png is an image reference relative to the DITA map URI. This information is automatically copied into the content in the merged map file, like this:

<topic is-chapter="true" ... >
    <title class="- topic/title ">Installing</title>
    <shortdesc class="- topic/shortdesc ">You install components to make them available for your
      solution.</shortdesc>
    <prolog class="- topic/prolog ">
      ...
      <data class="- topic/data " name="header-image" value="img/installing.png"/>
      ...

This information can be picked up from CSS:

/* Define the string set variable that contains an URL */
*[class ~= "topic/topic"] *[class ~= "topic/data"][name="header-image"] {
  string-set: imgst oxy_url(oxy_xpath('/*/@xtrf'), attr(value));
}

/* Using the value='none' stops applying the image. */
*[class ~= "topic/topic"] *[class ~= "topic/data"][name="header-image"][value="none"] {
  string-set: imgst "";
}

/* Use the string set variable in one of the page margin boxes. */
@page chapter {  
  @top-left-corner {
    content: string(imgst);
    font-size:0; /* remove the font ascent and descent */
  }
}

Details: The @value attribute is used to build a URL relative to the URI of the DITA map. To determine the base URI of the DITA map, the @xtrf attribute was used from the root element of the merged map document, extracted using the oxy_xpath function.

Notes:
  • The image is always aligned vertically to the middle of available space from the page margin box.
  • Make sure you use an image of the correct size. For example, if you want to place the image in the top-left corner of the page, assuming the top and left page margins are 1 in, then make sure the image is a square having a size of 1 in.
  • The image is applied to all pages that follow the data element, until another data element changes it:
    ...
     <chapter href="topics/installing.dita">
        <topicmeta>
          <data name="header-image" value="img/installing.png"/>
        </topicmeta>
     </chapter>
     <chapter href="..."> <!-- Uses the same installing.png image -->
     <chapter href="..."> <!-- Uses the same installing.png image -->
     <chapter href="..."> <!-- Uses the same installing.png image -->
     <chapter href="topics/change.dita">
        <topicmeta>
          <data name="header-image" value="img/change.png"/>
        </topicmeta>
     </chapter>
     <chapter href="..."> <!-- Uses the same change.png image -->
     <chapter href="..."> <!-- Uses the same change.png image -->
     <chapter href="..."> <!-- Uses the same change.png image -->
    To clear the image, use the none value:
    ...
          <data name="header-image" value="none"/>
    ...

Edit online

A common use-case is to add a background image to one of the page corners.

@page :left {
    @bottom-left-corner{
        content: " ";
        background-image: url('https://www.oxygenxml.com/resellers/resources/OxygenXMLEditor_icon.svg');
        background-repeat:no-repeat;
        background-position:50% 50%;        
    }
}
Important: Always specify a content property. If not, the page margin box will not be generated.

Another use-case is to use the @top-left or @top-right page margin boxes. These boxes have an automatic layout and they can be very small if they have no content. If there is no text to be placed over the image, use a series of non-breaking spaces (\A0) to increase the box width as in the following example (alternatively, you can use the technique described in How to Decorate the Header by Using a Background Image on the Entire Page):

@page :left {
    @top-left{
        content: '\A0\A0\A0\A0\A0\A0\A0\A0\A0\A0';
        background-image: url('https://www.oxygenxml.com/resellers/resources/OxygenXMLEditor_icon.svg');
        background-repeat:no-repeat;
        background-position:50% 50%;
    }
}
Note: You can use raster image formats (such as PNG or JPEG), but it is best to use vector images (such as SVG or PDF). They scale very well and produce better results when printed. In addition, the text from these images is searchable and can be selected (if the glyphs have not been converted to shapes) in the PDF viewer.

Edit online

If you need to style a fragment of text (for example, a company slogan) with certain colors or font styles, you have several options:
  • Use an SVG image as the background for a page margin box or for the entire page. See: How to Add a Background Image to the Header.
  • Use the oxy_label constructor. This is a function that creates a text label with a set of styles.
    @page {
        @top-right {
            content: oxy_label(text, "My Company", styles, "color:red; font-size: larger;") 
                     ' ' 
                     oxy_label(text, "Product", styles, "color:blue; text-decoration:underline;"));
        }
    }
    You can combine the oxy_label with oxy_xpath, to extract and style a piece of text from the document:
    content: oxy_label(text, oxy_xpath("/some/xpath"), styles, "color:blue; "));
    Note: These functions work only with the Chemistry CSS processor.
    Note: You cannot use string() inside an oxy_label(). As a workaround, to apply styling on the dynamic text retrieved by a string() function you can define some overall styles for the entire page margin box and then use the oxy_label to style differently the static text.
    @page {
        @top-right {
    	 color: red;
            content: oxy_label(text, "My Company", styles, "color:black") 
                     ' ' 
                     string(chaptertitle); /* This inherits the styling from @top-right*/
        }
    }
  • Use two adjacent page margin boxes, and style them differently:
    @page {
      @top-center {
        content: "First part";
        color: red;
        text-align:right;
      }
      @top-left {
        content: "- Second part";
        color: blue;
        text-align:left;
      }
    }

Edit online

The headers display information such as map title, part title, chapter title, and section title, ending in the page number.

content: string(maptitle) string(parttitle) string(chaptertitle) string(sectiontitle) " | " counter(page);

This might be too much if you have long titles. The solution is to override the default header content.

In your customization CSS, add the following CSS rule:

@page :left {
    @top-left {
        content: string(chaptertitle) " | " counter(page);
    }
}
@page :right{
    @top-right {
        content: string(chaptertitle) " | " counter(page);
    }
}
Important: Some of the CSS default page rules are more important. If you see that the content does not change:
  • Try to also specify the name of the page, to increase the specificity of the rules:
    @page :left, table-of-contents:left, chapter:left{
      ...
    }
    @page :right, table-of-contents:right, chapter:right{
      ...
    }
  • Add an !important classifier just before the semi-colon.
    @top-right {
        content: string(chaptertitle) " | " counter(page) !important;
    }

Edit online

There are some strings defined for part, chapter, and sections. Each of these strings start with the " | " character as a separator. For example, in the header of a page, you may find a sequence of strings:
My Publication | Introduction | Getting Started 
  1. "My Publication" is the value of the maptitle string.
  2. "Introduction" is the value of the chaptertitle string.
  3. "Getting Started" is the value of the sectiontitle string.

There might be cases where you want to change this separator. You will need to recompose the header content using the above string sets. Suppose you want to use " - " as a separator. In your customization CSS, add the following CSS rule:

*[class ~= "topic/topic"][is-part] > *[class ~= "topic/title"] {
    string-set: parttitle " - " counter(part, upper-roman) " - " content(), chaptertitle  ""; /* Avoid propagating a past chapter title on a new part */
}
*[class ~= "topic/topic"][is-chapter]:not([is-part]) > *[class ~= "topic/title"] {
    string-set: chaptertitle " - " counter(chapter) " - " content();
}

If you enabled the deep numbering for chapters and subsections, then use:

/* 
 * Alter the string sets that are shown in the header of the page.
 */
*[class ~= "map/map"][p|numbering^='deep'] *[class ~= "topic/topic"][is-part] > *[class ~= "topic/title"] {
    string-set: parttitle " - " counter(part, upper-roman) " - " content(), chaptertitle  "" ; /* Avoid propagating a past chapter title on a new part */
}
*[class ~= "map/map"][p|numbering^='deep'] *[class ~= "topic/topic"][is-chapter]:not([is-part]) > *[class ~= "topic/title"] {
    string-set: chaptertitle " - " counter(chapter) " - " content(), sectiontitle "" ;
}
*[class ~= "map/map"][p|numbering^='deep'] *[class ~= "topic/topic"][is-chapter]:not([is-part]) > *[class ~= "topic/topic"] > *[class ~= "topic/title"] {
    string-set: sectiontitle " - " counter(chapter) "." counter(section1) " - " content();
}

Edit online

To modify the styling of the default page headers, add the following CSS rule in your customization CSS:

@page :left {
    @top-left {
        color:navy;
        font-style:italic;
    }
    @top-right {
        color:red;
    }
}

If you intend to modify just the headers of the table of contents, use the table-of-contents page rule selector:

@page table-of-contents:left {
    @top-left {
        color:navy;
        font-style:italic;
    }
    @top-right {
        color:red;
    }
}

Edit online

A very simple approach is to use the oxy_xpath directly in the content property:

@page front-page {    	
     @top-center {
          content: "Created: " oxy_xpath('//*[contains(@class, " topic/created "][1]');
     }
}

Example 1: Compute the Number of Words

The following example computes the number of words from the publication. It counts all the words, including the ones from the TOC, but does not take the static labels into account:

@page front-page {
     @bottom-center {
          content: "Number of words: " 
                    oxy_xpath("string-length(normalize-space(/)) - \
                               string-length(translate(normalize-space(/),' ','')) +1");
     }
}
Note: The XPath expression from the page rules is evaluated in the context of the document root element, so you will need to use absolute expressions starting with / or //. This is different from the case when the oxy_xpath is used in CSS rules that match an element. In this case, the XPath expressions are evaluated in the context of the matched element and you can use relative paths.
Tip: XPath 2.0 is supported (not schema aware).

Example 2: Retrieve Image from a Document and Insert it in the Header

Another example is to use an image from the document in the publication header:
<bookmeta>
  <metadata>
   ...
    <data name="cover">
        <image href="product-cover.png" outputclass="cover-image"/>
    </data>
   ...
  </metadata>
</bookmeta>
@page {
   @top-center {
        content: url("oxy_xpath('//*[contains(@outputclass, "cover-image")]/@href')");
   }
}

If the URL returned by oxy_xpath is not absolute, it is considered to be relative to the CSS file. To obtain an absolute URL from one relative to the XML document, you can use in the XPath expression functions like resolve-uri and document-uri:

@page {
   @top-center {
        content: url(oxy_xpath("resolve-uri(//*[contains(@outputclass, 'cover-image')]/@href), document-uri(/))"));
   }
}

Example 3: Insert the Current Date in the Footer

Another example is to use the oxy_xpath function to compute the current date and insert it in the publication footer:

@page {
    @bottom-left {
        content: oxy_xpath('current-date()');
    }
}

Example 4: Picking up Metadata from the Original Map

Another example is to use the oxy_xpath function to extract the title, or any other element text value from the original processed DITA map file. For this, you can use the @xtrf attribute that is set on the root element of the merged map. This attribute contains the URL of the input map.

:root{
        string-set: maptitle oxy_xpath('document(@xtrf)/*[contains(@class, " map/map ")]/*[contains(@class, " topic/title ")]/text()');
}

Edit online

There are two ways to add a horizontal line under the header.

Method 1: Add a Border in the Page Margin Boxes

To add a horizontal line that would stretch across the width of the page, add a bottom border to each of the 5 margin boxes in the top side of the page (top-left-corner, top-left, top-center, top-right, top-right-corner).

If you consider that the space between the header and the bottom border is too large, you could also change the alignment by adding a vertical-align: bottom; declaration in the page margin boxes.

For example, if you need to set some text as a header in the top-left margin box and insert a horizontal line under it, the customization CSS would look something like this:
@page chapter, chapter:first:left:right, front-page{

   padding-top: 1em;
 
  @top-left {
    content: "Custom header";
    color: gray;
    border-bottom: 1px solid black;
    vertical-align: bottom;
  }

  @top-center{
    content:" ";
    border-bottom: 1px solid black;
    vertical-align: bottom;
  }

  @top-right{
    content:" ";
    border-bottom: 1px solid black;
    vertical-align: bottom;
  }

  @top-right-corner{
    content:" ";
    border-bottom: 1px solid black;
    vertical-align: bottom;
  }

  @top-left-corner{
    content:" ";
    border-bottom: 1px solid black;
    vertical-align: bottom;
  }
Note: The padding-top: 1em; is used to avoid the border at the bottom of the header that joins with the page content.

Method 2: Use a Background Image

An alternative method is to add a horizontal line/border under an existing header (or in any other part of the page) using an SVG image, as described in How to Add a Background Image to the Header.