diff --git a/demo/lib/App.tsx b/demo/lib/App.tsx
index b10b7dcfd..7ed198e7a 100644
--- a/demo/lib/App.tsx
+++ b/demo/lib/App.tsx
@@ -17,9 +17,11 @@ import seqparse from "seqparse";
 import Circular from "../../src/Circular/Circular";
 import Linear from "../../src/Linear/Linear";
 import SeqViz from "../../src/SeqViz";
-import { AnnotationProp } from "../../src/elements";
+import { AnnotationProp, Primer } from "../../src/elements";
 import Header from "./Header";
 import file from "./file";
+import { Direction } from "../../src/Linear/Primers";
+import { COLORS, chooseRandomColor } from "../../src/colors";
 
 const viewerTypeOptions = [
   { key: "both", text: "Both", value: "both" },
@@ -33,6 +35,7 @@ interface AppState {
   customChildren: boolean;
   enzymes: any[];
   name: string;
+  primers: Primer[]
   search: { query: string };
   searchResults: any;
   selection: any;
@@ -52,6 +55,24 @@ export default class App extends React.Component<any, AppState> {
     customChildren: true,
     enzymes: ["PstI", "EcoRI", "XbaI", "SpeI"],
     name: "",
+    primers: [
+      {
+        color: chooseRandomColor(),
+        direction: Direction.FOWARD,
+        end: 653,
+        id: "527923581",
+        name: "pLtetO-1 fw primer",
+        start: 633,
+      }, 
+      {
+        color: chooseRandomColor(),
+        direction: Direction.REVERSE,
+        end: 706,
+        id: "527923582",
+        name: "pLtetO-1 rev primer",
+        start: 686,
+      }, 
+    ],
     search: { query: "ttnnnaat" },
     searchResults: {},
     selection: {},
@@ -73,6 +94,7 @@ export default class App extends React.Component<any, AppState> {
 
   componentDidMount = async () => {
     const seq = await seqparse(file);
+
     this.setState({ annotations: seq.annotations, name: seq.name, seq: seq.seq });
   };
 
@@ -215,6 +237,7 @@ export default class App extends React.Component<any, AppState> {
                     // accession="MN623123"
                     key={`${this.state.viewer}${this.state.customChildren}`}
                     annotations={this.state.annotations}
+                    primers={this.state.primers}
                     enzymes={this.state.enzymes}
                     highlights={[{ start: 0, end: 10 }]}
                     name={this.state.name}
diff --git a/src/Linear/Linear.tsx b/src/Linear/Linear.tsx
index 3ad8e9e3a..d0f8a9652 100644
--- a/src/Linear/Linear.tsx
+++ b/src/Linear/Linear.tsx
@@ -1,12 +1,13 @@
 import * as React from "react";
 
 import { InputRefFunc } from "../SelectionHandler";
-import { Annotation, CutSite, Highlight, NameRange, Range, SeqType, Size } from "../elements";
+import { Annotation, CutSite, Highlight, NameRange, Primer, Range, SeqType, Size } from "../elements";
 import { createMultiRows, createSingleRows, stackElements } from "../elementsToRows";
 import { isEqual } from "../isEqual";
 import { createTranslations } from "../sequence";
 import { InfiniteScroll } from "./InfiniteScroll";
 import { SeqBlock } from "./SeqBlock";
+import { Direction } from "./Primers";
 
 export interface LinearProps {
   annotations: Annotation[];
@@ -21,6 +22,7 @@ export interface LinearProps {
   inputRef: InputRefFunc;
   lineHeight: number;
   onUnmount: (id: string) => void;
+  primers: Primer[];
   search: NameRange[];
   seq: string;
   seqFontSize: number;
@@ -68,6 +70,7 @@ export default class Linear extends React.Component<LinearProps> {
       highlights,
       lineHeight,
       onUnmount,
+      primers,
       search,
       seq,
       seqType,
@@ -99,7 +102,7 @@ export default class Linear extends React.Component<LinearProps> {
     /**
      * Vet the annotations for starts and ends at zero index
      */
-    const vetAnnotations = (annotations: Annotation[]) => {
+    const vetAnnotations = (annotations: Annotation[] | Primer[]) => {
       annotations.forEach(ann => {
         if (ann.end === 0 && ann.start > ann.end) ann.end = seqLength;
         if (ann.start === seqLength && ann.end < ann.start) ann.start = 0;
@@ -107,6 +110,23 @@ export default class Linear extends React.Component<LinearProps> {
       return annotations;
     };
 
+    const primerFwRows = createMultiRows(
+      stackElements(vetAnnotations(
+        primers.filter((p) => p.direction == Direction.FOWARD)),
+        seq.length
+      ),
+      bpsPerBlock,
+      arrSize
+    ) 
+    const primerRvRows = createMultiRows(
+      stackElements(vetAnnotations(
+        primers.filter((p) => p.direction == Direction.REVERSE)),
+        seq.length
+      ),
+      bpsPerBlock,
+      arrSize
+    ) 
+
     const annotationRows = createMultiRows(
       stackElements(vetAnnotations(annotations), seq.length),
       bpsPerBlock,
@@ -141,6 +161,12 @@ export default class Linear extends React.Component<LinearProps> {
       if (zoomed) {
         blockHeight += showComplement ? lineHeight : 0; // double for complement + 2px margin
       }
+      if (primerFwRows[i].length) {
+        blockHeight += lineHeight;
+      }
+      if (primerRvRows[i].length) {
+        blockHeight += lineHeight;
+      }
       if (showIndex) {
         blockHeight += lineHeight; // another for index row
       }
@@ -164,6 +190,8 @@ export default class Linear extends React.Component<LinearProps> {
       seqBlocks.push(
         <SeqBlock
           key={ids[i]}
+          primerFwRows={primerFwRows[i]}
+          primerRvRows={primerRvRows[i]}
           annotationRows={annotationRows[i]}
           blockHeight={blockHeights[i]}
           bpColors={this.props.bpColors}
diff --git a/src/Linear/Primers.tsx b/src/Linear/Primers.tsx
new file mode 100644
index 000000000..51e45a465
--- /dev/null
+++ b/src/Linear/Primers.tsx
@@ -0,0 +1,268 @@
+
+import * as React from "react";
+
+import { InputRefFunc } from "../SelectionHandler";
+import { COLOR_BORDER_MAP, darkerColor } from "../colors";
+import { NameRange } from "../elements";
+import { annotation, annotationLabel } from "../style";
+import { FindXAndWidthElementType } from "./SeqBlock";
+
+const hoverOtherAnnotationRows = (className: string, opacity: number) => {
+  if (!document) return;
+  const elements = document.getElementsByClassName(className) as HTMLCollectionOf<HTMLElement>;
+  for (let i = 0; i < elements.length; i += 1) {
+    elements[i].style.fillOpacity = `${opacity}`;
+  }
+};
+
+export enum Direction {
+  FOWARD = 1,
+  REVERSE = -1
+}
+/**
+ * Render each row of annotations into its own row.
+ * This is not a default export for sake of the React component displayName.
+ */
+const PrimeRows = (props: {
+  primerRows: NameRange[][];
+  direction: Direction
+  bpsPerBlock: number;
+  elementHeight: number;
+  findXAndWidth: FindXAndWidthElementType;
+  firstBase: number;
+  fullSeq: string;
+  inputRef: InputRefFunc;
+  lastBase: number;
+  seqBlockRef: unknown;
+  width: number;
+  yDiff: number;
+}) => (
+  <g>
+    {props.primerRows.map((primers: NameRange[], i: number) => (
+      <PrimerRow
+        key={`annotation-linear-row-${primers[0].id}-${props.firstBase}-${props.lastBase}`}
+        primers={primers}
+        direction={props.direction}
+        bpsPerBlock={props.bpsPerBlock}
+        findXAndWidth={props.findXAndWidth}
+        firstBase={props.firstBase}
+        fullSeq={props.fullSeq}
+        height={props.elementHeight}
+        inputRef={props.inputRef}
+        lastBase={props.lastBase}
+        seqBlockRef={props.seqBlockRef}
+        width={props.width}
+        y={props.yDiff + props.elementHeight * i}
+      />
+    ))}
+  </g>
+);
+
+export default PrimeRows;
+
+/**
+ * A single row of annotations. Multiple of these may be in one seqBlock
+ * vertically stacked on top of one another in non-overlapping arrays.
+ */
+const PrimerRow = (props: {
+  primers: NameRange[];
+  direction: Direction;
+  bpsPerBlock: number;
+  findXAndWidth: FindXAndWidthElementType;
+  firstBase: number;
+  fullSeq: string;
+  height: number;
+  inputRef: InputRefFunc;
+  lastBase: number;
+  seqBlockRef: unknown;
+  width: number;
+  y: number;
+}) => {
+  return (
+    <g
+      className="la-vz-linear-annotation-row"
+      height={props.height * 0.8}
+      transform={`translate(0, ${props.y})`}
+      width={props.width}
+    >
+      {props.primers.filter((a) => a.direction == props.direction).map((a, i) => (
+                <SingleNamedElement
+                  {...props} // include overflowLeft in the key to avoid two split annotations in the same row from sharing a key
+                  key={`annotation-linear-${a.id}-${i}-${props.firstBase}-${props.lastBase}`}
+                  element={a}
+                  elements={props.primers}
+                  index={i}
+                />
+              )
+      )}
+    </g>
+  );
+} 
+/**
+ * SingleNamedElement is a single rectangular element in the SeqBlock.
+ * It does a bunch of stuff to avoid edge-cases from wrapping around the 0-index, edge of blocks, etc.
+ */
+const SingleNamedElement = (props: {
+  element: NameRange;
+  elements: NameRange[];
+  findXAndWidth: FindXAndWidthElementType;
+  firstBase: number;
+  height: number;
+  index: number;
+  inputRef: InputRefFunc;
+  lastBase: number;
+}) => {
+  const { element, elements, findXAndWidth, firstBase, index, inputRef, lastBase } = props;
+
+  const { color, direction, end, name, start } = element;
+  const forward = direction === Direction.FOWARD;
+  const reverse = direction === Direction.REVERSE;
+  const { overflowLeft, overflowRight, width, x: origX } = findXAndWidth(index, element, elements);
+  const crossZero = start > end && end < firstBase;
+
+  // does the element begin or end within this seqBlock with a directionality?
+  const endFWD = forward && end > firstBase && end <= lastBase;
+  const endREV = reverse && start >= firstBase && start <= lastBase;
+
+  // create padding on either side, vertically, of an element
+  const height = props.height * 0.8;
+
+  const cW = 4; // jagged cutoff width
+  const cH = height / 4; // jagged cutoff height
+  const [x, w] = [origX, width];
+
+  // create the SVG path, starting at the topLeft and working clockwise
+  // there is additional logic here for if the element overflows
+  // to the left or right of this seqBlock, where a "jagged edge" is created
+  const topLeft = "M 0 0";
+  let topRight = endFWD ? 
+    `
+      L ${width - Math.min(8 * cW, w)} 0
+      L ${width - Math.min(8 * cW, w)} ${-1 * height}
+    ` :
+    `L ${width} 0`;
+
+  let linePath = "";
+
+  let bottomRight = `L ${width} ${height}`; // flat right edge
+  if ((overflowRight && width > 2 * cW) || crossZero) {
+    bottomRight = `
+        L ${width - cW} ${cH}
+        L ${width} ${2 * cH}
+        L ${width - cW} ${3 * cH}
+        L ${width} ${4 * cH}`; // jagged right edge
+  } else if (endFWD) {
+    bottomRight = `
+        L ${width} ${height}`; // arrow forward
+  }
+
+  let bottomLeft = `L 0 ${height} L 0 0`; // flat left edge
+  if (overflowLeft && width > 2 * cW) {
+    bottomLeft = `
+        L 0 ${height}
+        L ${cW} ${3 * cH}
+        L 0 ${2 * cH}
+        L ${cW} ${cH}
+        L 0 0`; // jagged left edge
+  } else if (endREV) {
+    bottomLeft = `
+        L ${Math.min(8 * cW, w)} ${height}
+        L ${Math.min(8 * cW, w)} ${height * 2}`; // arrow reverse
+  }
+
+  linePath = `${topLeft} ${topRight} ${bottomRight} ${bottomLeft}`;
+
+  if ((forward && overflowRight) || (forward && crossZero)) {
+    // If it's less than 15 pixels the double arrow barely fits
+    if (width > 15) {
+      linePath += `
+        M ${width - 3 * cW} ${cH}
+        L ${width - 2 * cW} ${2 * cH}
+        L ${width - 3 * cW} ${3 * cH}
+        M ${width - 4 * cW} ${cH}
+        L ${width - 3 * cW} ${2 * cH}
+        L ${width - 4 * cW} ${3 * cH}`; // add double arrow forward
+    }
+  }
+  if ((reverse && overflowLeft) || (reverse && crossZero)) {
+    // If it's less than 15 pixels the double arrow barely fits
+    if (width > 15) {
+      linePath += `
+        M ${3 * cW} ${3 * cH}
+        L ${2 * cW} ${cH * 2}
+        L ${3 * cW} ${cH}
+        M ${4 * cW} ${3 * cH}
+        L ${3 * cW} ${cH * 2}
+        L ${4 * cW} ${cH}`; // add double forward reverse
+    }
+  }
+  // 0.591 is our best approximation of Roboto Mono's aspect ratio (width / height).
+  const fontSize = 12;
+  const annotationCharacterWidth = 0.591 * fontSize;
+  const availableCharacters = Math.floor((width - 40) / annotationCharacterWidth);
+
+  // Ellipsize or hide the name if it's too long.
+  let displayName = name;
+  if (name.length > availableCharacters) {
+    const charactersToShow = availableCharacters - 1;
+    if (charactersToShow < 3) {
+      // If we can't show at least three characters, don't show any.
+      displayName = "";
+    } else {
+      displayName = `${name.slice(0, charactersToShow)}…`;
+    }
+  }
+
+  return (
+    <g id={element.id} transform={`translate(${x}, ${0.1 * height})`}>
+      {/* <title> provides a hover tooltip on most browsers */}
+      <title>{name}</title>
+      <path
+        ref={inputRef(element.id, {
+          end: end,
+          name: element.name,
+          ref: element.id,
+          start: start,
+          type: "ANNOTATION",
+          viewer: "LINEAR",
+        })}
+        className={`${element.id} la-vz-annotation`}
+        cursor="pointer"
+        d={linePath}
+        fill={color}
+        id={element.id}
+        stroke={color ? COLOR_BORDER_MAP[color] || darkerColor(color) : "gray"}
+        style={annotation}
+        onBlur={() => {
+          // do nothing
+        }}
+        onFocus={() => {
+          // do nothing
+        }}
+        onMouseOut={() => hoverOtherAnnotationRows(element.id, 0.7)}
+        onMouseOver={() => hoverOtherAnnotationRows(element.id, 1.0)}
+      />
+      <text
+        className="la-vz-annotation-label"
+        cursor="pointer"
+        dominantBaseline="middle"
+        fontSize={fontSize}
+        id={element.id}
+        style={annotationLabel}
+        textAnchor="middle"
+        x={width / 2}
+        y={height / 2 + 1}
+        onBlur={() => {
+          // do nothing
+        }}
+        onFocus={() => {
+          // do nothing
+        }}
+        onMouseOut={() => hoverOtherAnnotationRows(element.id, 0.7)}
+        onMouseOver={() => hoverOtherAnnotationRows(element.id, 1.0)}
+      >
+        {displayName}
+      </text>
+    </g>
+  );
+};
diff --git a/src/Linear/SeqBlock.tsx b/src/Linear/SeqBlock.tsx
index f1bbec3af..0a6ddfe7f 100644
--- a/src/Linear/SeqBlock.tsx
+++ b/src/Linear/SeqBlock.tsx
@@ -10,6 +10,7 @@ import { Highlights } from "./Highlights";
 import IndexRow from "./Index";
 import Selection from "./Selection";
 import { TranslationRows } from "./Translations";
+import PrimeRows, { Direction } from "./Primers";
 
 export type FindXAndWidthType = (
   n1?: number | null,
@@ -27,6 +28,8 @@ export type FindXAndWidthElementType = (
 
 interface SeqBlockProps {
   annotationRows: Annotation[][];
+  primerFwRows: Annotation[][];
+  primerRvRows: Annotation[][];
   blockHeight: number;
   bpColors?: { [key: number | string]: string };
   bpsPerBlock: number;
@@ -215,6 +218,8 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
   render() {
     const {
       annotationRows,
+      primerFwRows,
+      primerRvRows,
       blockHeight,
       bpsPerBlock,
       charWidth,
@@ -257,16 +262,23 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
     const cutSiteYDiff = 0; // spacing for cutSite names
     const cutSiteHeight = zoomed && cutSiteRows.length ? lineHeight : 0;
 
+    //height and yDiff of foward primers
+    const primerFwYDiff = cutSiteYDiff + cutSiteHeight;
+    const primerFwHeight = primerFwRows.length ? elementHeight : 0;
+
     // height and yDiff of the sequence strand
-    const indexYDiff = cutSiteYDiff + cutSiteHeight;
+    const indexYDiff = primerFwYDiff + primerFwHeight;
     const indexHeight = seqType === "aa" ? 0 : lineHeight; // if aa, no seq row is shown
 
     // height and yDiff of the complement strand
     const compYDiff = indexYDiff + indexHeight;
     const compHeight = zoomed && showComplement ? lineHeight : 0;
+    //height and yDiff of reverse primers
+    const primerRvYDiff = compYDiff + compHeight;
+    const primerRvHeight = primerRvRows.length ? elementHeight * 2 : 0;
 
     // height and yDiff of translations
-    const translationYDiff = compYDiff + compHeight;
+    const translationYDiff = primerRvYDiff + primerRvHeight;
     const translationHeight = elementHeight * translationRows.length;
 
     // height and yDiff of annotations
@@ -274,12 +286,13 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
     const annHeight = elementHeight * annotationRows.length;
 
     // height and ydiff of the index row.
-    const elementGap = annotationRows.length + translationRows.length ? 3 : 0;
+    const elementGap = primerRvRows.length + primerRvRows.length + annotationRows.length + translationRows.length ? 3 : 0;
+
     const indexRowYDiff = annYDiff + annHeight + elementGap;
 
     // calc the height necessary for the sequence selection
     // it starts 5 above the top of the SeqBlock
-    const selectHeight = cutSiteHeight + indexHeight + compHeight + translationHeight + annHeight + elementGap + 5;
+    const selectHeight = cutSiteHeight + indexHeight + compHeight + translationHeight + annHeight + primerFwHeight + primerRvHeight + elementGap + 5;
     let selectEdgeHeight = selectHeight + 9; // +9 is the height of a tick + index row
 
     // needed because otherwise the selection height is very small
@@ -323,6 +336,22 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
             zoom={zoom}
           />
         )}
+        {primerFwRows.length && (
+          <PrimeRows
+              primerRows={primerFwRows}
+              direction={Direction.FOWARD}              
+              bpsPerBlock={bpsPerBlock}
+              elementHeight={elementHeight}
+              findXAndWidth={this.findXAndWidthElement}
+              firstBase={firstBase}
+              fullSeq={fullSeq}
+              inputRef={inputRef}
+              lastBase={lastBase}
+              seqBlockRef={this}
+              width={size.width}
+              yDiff={primerFwYDiff}
+          />
+        )}
         <Selection.Block
           findXAndWidth={this.findXAndWidth}
           firstBase={firstBase}
@@ -362,6 +391,22 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
           listenerOnly={false}
           zoomed={zoomed}
         />
+        {primerRvRows.length && (
+          <PrimeRows
+              primerRows={primerRvRows}
+              direction={Direction.REVERSE}              
+              bpsPerBlock={bpsPerBlock}
+              elementHeight={elementHeight}
+              findXAndWidth={this.findXAndWidthElement}
+              firstBase={firstBase}
+              fullSeq={fullSeq}
+              inputRef={inputRef}
+              lastBase={lastBase}
+              seqBlockRef={this}
+              width={size.width}
+              yDiff={primerRvYDiff}
+          />
+        )}
         {translationRows.length && (
           <TranslationRows
             bpsPerBlock={bpsPerBlock}
@@ -393,6 +438,7 @@ export class SeqBlock extends React.PureComponent<SeqBlockProps> {
             yDiff={annYDiff}
           />
         )}
+        
         {zoomed && seqType !== "aa" ? (
           <text
             {...textProps}