# Appearance Streams

## Content

Appearance streams define the visual representation of annotations in a PDF document. They allow annotations to render a consistent appearance across PDF viewers.
Annotations can define appearance streams for the following states:

* Normal: displayed when the annotation isn't interacting with the user.
* Rollover: displayed when the pointer moves over the annotation.
* Down: displayed while the annotation is pressed.

Appearance streams are represented by FormXObject instances. Use the [appearanceStreams](/document-solutions/javascript-pdf-api/api/classes/AnnotationBase#appearancestreams) property to assign appearance streams to annotation states.
For more information about FormXObject, see [Form XObjects](/document-solutions/javascript-pdf-api/docs/features/form-xobjects).

## Create Annotation Appearance Streams

To create annotation appearance streams:

1. Create a [FormXObject](/document-solutions/javascript-pdf-api/api/classes/FormXObject) instance.
2. Generate appearance content using the FormXObject context.
3. Assign the FormXObject to an annotation appearance state using the [appearanceStreams](/document-solutions/javascript-pdf-api/api/classes/AnnotationBase#appearancestreams) property.

The following example demonstrates how to create and assign custom appearance streams for different annotation states.

```auto
// Custom annotation appearance streams: full control over the normal ("N"),
// rollover ("R") and down ("D") appearance streams of an annotation.
// An appearance stream is a FormXObject, so the full drawing API is available.
const doc = new PdfDocument();
const page = doc.pages.addNew();
const font = Font.getPdfFont(StandardPdfFont.Helvetica);
page.context.drawText('A push button with custom normal / rollover / down appearance streams', new Format({ font: font, fontSize: 12 }), 20, 20);
page.context.drawText('(hover over and press the button in a PDF viewer to see them):', new Format({ font: font, fontSize: 12 }), 20, 35);
const btn = new PushButtonField();
btn.name = 'btn';
btn.alternateName = 'Button with custom appearance';
btn.widget.rect = { x: 20, y: 60, width: 120, height: 30 };
btn.widget.highlighting = HighlightingMode.Push;
btn.widget.border = { style: BorderStyle.Underline };
btn.widget.page = page;
// Builds one appearance stream: a FormXObject with a gradient fill and a border.
function createAppearance(startColor, endColor, borderColor) {
    const fxo = new FormXObject(doc, { x: 0, y: 0, width: btn.widget.rect.width, height: btn.widget.rect.height });
    const g = fxo.context;
    const r = { x: 0, y: 0, width: g.width, height: g.height };
    g.drawRect(r, { fillBrush: new LinearGradientBrush({ startColor: startColor, endColor: endColor }) });
    g.drawRect(r, { lineColor: borderColor, lineWidth: 3 });
    return fxo;
}
// Each AppearanceStream entry can hold a single default stream
// (setting null removes it); for stateful annotations such as check boxes
// use getStream(name) / setStream(name, formXObject) instead.
btn.widget.appearanceStreams.normal.default = createAppearance('LightCyan', 'Cyan', 'DarkCyan');
btn.widget.appearanceStreams.rollover.default = createAppearance('Cyan', 'LightCyan', 'DarkCyan');
btn.widget.appearanceStreams.down.default = createAppearance('Cyan', 'LightCyan', 'Red');
doc.acroForm.fields.add(btn);
const outputFile = path.join(__dirname, 'AppearanceStreams.pdf');
await writeFile(outputFile, doc.savePdf());
console.log(`Created ${outputFile}`);
```

![DsPdfJS demonstration of PDF annotations with custom appearance streams assigned to different annotation interaction states, showing visual changes for normal, rollover hover, and pressed states within a PDF document](https://cdn.mescius.io/document-site-files/images/f5820aa7-2bd1-4325-91b3-71d51a001343/DsPdfJS_Annotations_AppearanceStreams-20260723.707c4b.gif?width=720)