diff --git a/src/Renderer/Html/InteractiveSideBySide.php b/src/Renderer/Html/InteractiveSideBySide.php
new file mode 100644
index 0000000..a3fbbf1
--- /dev/null
+++ b/src/Renderer/Html/InteractiveSideBySide.php
@@ -0,0 +1,408 @@
+<?php
+/**
+ * Extends the side by side renderer to add a checkbox to each row.
+ *
+ * @package EaLab\WP_Deployer
+ */
+
+declare(strict_types=1);
+
+namespace Jfcherng\Diff\Renderer\Html;
+
+use Jfcherng\Diff\SequenceMatcher;
+
+/**
+ * Interactive Side by Side HTML diff generator.
+ * 
+ * This generator REQUIRES JsonHtmlWithRaw renderer as the lines provider.
+ */
+final class InteractiveSideBySide extends AbstractHtml
+{
+	protected int $currentDiffLineNumber = 0;
+
+    /**
+     * {@inheritdoc}
+     */
+    public const INFO = [
+        'desc' => 'Interactive side by side',
+        'type' => 'Html',
+    ];
+
+	/**
+	 * Builds an encoded value for the HTML attribute.
+	 *
+	 * @param string|null $value The value to build.
+	 *
+	 * @return string The built value.
+	 */
+	protected function buildValueForAttribute(?string $value): string {
+		// Encapsulate the value into a structured array to support null value
+		// in the input value.
+		if ($value === null) {
+			$jsonEncapsulatedValue = json_encode(['value' => null]);
+		} else {
+			$jsonEncapsulatedValue = json_encode(['value' => $value]);
+		}
+
+		// Encode the value for HTML attribute.
+		return $this->encode($jsonEncapsulatedValue);
+	}
+
+	/**
+	 * Encode string.
+	 *
+	 * @param string $string The string to encode.
+	 * @param bool $forInputName (Optional) Whether the string is for input name. Defaults to false.
+	 *
+	 * @return string The encoded string.
+	 */
+	protected function encode(string $string, bool $forInputName = false): string {
+		$encodeForInputName = function (string $input): string {
+			// PHP converts the dot in the input name to underscore in the $_POST array,
+			// so we need to convert the dot to something else.
+			return str_replace(['.'], ['<dsc:dot>'], $input);
+		};
+
+		return htmlspecialchars(
+			$forInputName ? $encodeForInputName($string) : $string,
+			ENT_QUOTES,
+			'UTF-8');
+	}
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function redererChanges(array $changes): string
+    {
+        if (empty($changes)) {
+            return $this->getResultForIdenticals();
+        }
+
+        $wrapperClasses = [
+            ...$this->options['wrapperClasses'],
+            'diff', 'diff-html', 'diff-interactive-side-by-side',
+        ];
+
+	    $this->currentDiffLineNumber = 0;
+
+        return
+            '<table class="' . implode(' ', $wrapperClasses) . '">' .
+                $this->renderTableHeader() .
+                $this->renderTableHunks($changes) .
+            '</table>' .
+            $this->renderMetadata();
+    }
+
+	/**
+	 * Renders metadata not directly related to the diff.
+	 *
+	 * Dev note: This can be moved out of this class, as metadata
+	 * are not directly related to the diff.
+	 * I.e. move the implementation to the caller which can then
+	 * specify the metadata to render (by injecting callbacks).
+	 *
+	 * @return string The rendered metadata.
+	 */
+	protected function renderMetadata(): string {
+		$metadata = '';
+
+		if (!empty($this->options['uses_php_serialized_processor'])) {
+			// Add a hidden input to tell that the field is reserialized
+			// by the PHP serialized processor.
+			$inputName = ($this->options['diff_id'] ?? '');
+
+			if (!empty($inputName)) {
+				$metadata .= '<input type="hidden" name="uses_php_serialized_processor[]" value="' . $this->encode( $inputName ) . '">';
+			}
+		}
+
+		return $metadata;
+	}
+
+	/**
+	 * Renders the radio input.
+	 *
+	 * @param string $inputName The input name.
+	 * @param string|null $inputValue The input value. May be null.
+	 * @param string|null $checkedValue (Optional) The checked value, if any.
+	 *
+	 * @return string The rendered radio input.
+	 */
+	protected function renderRadioInput(string $inputName, ?string $inputValue, ?string $checkedValue = null): string
+	{
+		$attrChecked = $inputValue === $checkedValue ? ' checked="checked"' : '';
+
+		$attrName = ' name="merge[' . $this->encode($inputName, true) . ']"';
+
+		$attrValue = ' value="' . $this->buildValueForAttribute($inputValue) . '"';
+
+		return '<input type="radio"' . $attrName . $attrValue . $attrChecked . '/>';
+	}
+
+    /**
+     * Renderer the table header.
+     */
+    protected function renderTableHeader(): string
+    {
+        if (!$this->options['showHeader']) {
+            return '';
+        }
+
+        return
+            '<thead>' .
+                '<tr>' .
+                    '<th>' . $this->_('old_version') . '</th>' .
+                    ($this->options['lineNumbers'] ? '<th></th>' : '') .
+                    '<th></th>' .
+                    '<th></th>' .
+                    '<th>' . $this->_('new_version') . '</th>' .
+                    ($this->options['lineNumbers'] ? '<th></th>' : '') .
+                '</tr>' .
+            '</thead>';
+    }
+
+    /**
+     * Renderer the table separate block.
+     */
+    protected function renderTableSeparateBlock(): string
+    {
+        $colspan = $this->options['lineNumbers'] ? '6' : '4';
+
+        return
+            '<tbody class="skipped">' .
+                '<tr>' .
+                    '<td colspan="' . $colspan . '"></td>' .
+                '</tr>' .
+            '</tbody>';
+    }
+
+    /**
+     * Renderer table hunks.
+     *
+     * @param array[][] $hunks each hunk has many blocks
+     */
+    protected function renderTableHunks(array $hunks): string
+    {
+        $ret = '';
+
+        foreach ($hunks as $i => $hunk) {
+            if ($i > 0 && $this->options['separateBlock']) {
+                $ret .= $this->renderTableSeparateBlock();
+            }
+
+            foreach ($hunk as $block) {
+                $ret .= $this->renderTableBlock($block);
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlock(array $block): string
+    {
+        switch ($block['tag']) {
+            case SequenceMatcher::OP_EQ:
+                $content = $this->renderTableBlockEqual($block);
+                break;
+            case SequenceMatcher::OP_INS:
+                $content = $this->renderTableBlockInsert($block);
+                break;
+            case SequenceMatcher::OP_DEL:
+                $content = $this->renderTableBlockDelete($block);
+                break;
+            case SequenceMatcher::OP_REP:
+                $content = $this->renderTableBlockReplace($block);
+                break;
+            default:
+                $content = '';
+        }
+
+        return '<tbody class="change change-' . self::TAG_CLASS_MAP[$block['tag']] . '">' . $content . '</tbody>';
+    }
+
+    /**
+     * Renderer the table block: equal.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockEqual(array $block): string
+    {
+        $ret = '';
+
+        $rowCount = \count($block['new']['lines']);
+
+        for ($no = 0; $no < $rowCount; ++$no) {
+            $ret .= $this->renderTableRow(
+                $block['old']['lines'][$no],
+                $block['new']['lines'][$no],
+                $block['old']['offset'] + $no + 1,
+                $block['new']['offset'] + $no + 1,
+	            $block['old']['raw_lines'][$no] ?? null,
+	            $block['new']['raw_lines'][$no] ?? null,
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block: insert.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockInsert(array $block): string
+    {
+        $ret = '';
+
+        foreach ($block['new']['lines'] as $no => $newLine) {
+            $ret .= $this->renderTableRow(
+                null,
+                $newLine,
+                null,
+                $block['new']['offset'] + $no + 1,
+	            null,
+	            $block['new']['raw_lines'][$no] ?? null,
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block: delete.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockDelete(array $block): string
+    {
+        $ret = '';
+
+        foreach ($block['old']['lines'] as $no => $oldLine) {
+            $ret .= $this->renderTableRow(
+                $oldLine,
+                null,
+                $block['old']['offset'] + $no + 1,
+                null,
+	            $block['old']['raw_lines'][$no] ?? null,
+	            null
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block: replace.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockReplace(array $block): string
+    {
+        $ret = '';
+
+        $lineCountMax = max(\count($block['old']['lines']), \count($block['new']['lines']));
+
+        for ($no = 0; $no < $lineCountMax; ++$no) {
+            if (isset($block['old']['lines'][$no])) {
+                $oldLineNum = $block['old']['offset'] + $no + 1;
+                $oldLine = $block['old']['lines'][$no];
+				$oldLineRaw = $block['old']['raw_lines'][$no] ?? null;
+            } else {
+                $oldLineNum = $oldLine = $oldLineRaw = null;
+            }
+
+            if (isset($block['new']['lines'][$no])) {
+                $newLineNum = $block['new']['offset'] + $no + 1;
+                $newLine = $block['new']['lines'][$no];
+				$newLineRaw = $block['new']['raw_lines'][$no] ?? null;
+            } else {
+                $newLineNum = $newLine = $newLineRaw = null;
+            }
+
+            $ret .= $this->renderTableRow($oldLine, $newLine, $oldLineNum, $newLineNum, $oldLineRaw, $newLineRaw);
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer a content row of the output table.
+     *
+     * @param null|string $oldLine    the old line
+     * @param null|string $newLine    the new line
+     * @param null|int    $oldLineNum the old line number
+     * @param null|int    $newLineNum the new line number
+     * @param null|string $oldLineRaw the old line raw
+     * @param null|string $newLineRaw the new line raw
+     */
+    protected function renderTableRow(
+        ?string $oldLine,
+        ?string $newLine,
+        ?int $oldLineNum,
+        ?int $newLineNum,
+	    ?string $oldLineRaw,
+	    ?string $newLineRaw
+    ): string {
+		$diffLineId = ($this->options['diff_id'] ?? '') . ':diff-line_' . ++$this->currentDiffLineNumber;
+
+		if ( $oldLine === $newLine ) {
+			// No change means we can include the value in a hidden input.
+			$hiddenInput = '<input type="hidden" name="merge[' . $this->encode($diffLineId, true) . ']" value="' . $this->buildValueForAttribute($oldLineRaw) . '">';
+			$middleColumns = $this->renderLineContentColumn('unchanged', $hiddenInput) . $this->renderLineContentColumn('empty', '');
+		} else {
+			// Provide the radio buttons for the user to select the value.
+			$middleColumns =
+				$this->renderLineContentColumn('radio-old', $this->renderRadioInput($diffLineId, $oldLineRaw)) .
+			    $this->renderLineContentColumn('radio-new', $this->renderRadioInput($diffLineId, $newLineRaw, $newLineRaw));
+		}
+
+        return
+            '<tr>' .
+                (
+                    $this->options['lineNumbers']
+                        ? $this->renderLineNumberColumn('old', $oldLineNum)
+                        : ''
+                ) .
+                $this->renderLineContentColumn('old', $oldLine) .
+                $middleColumns .
+                (
+                    $this->options['lineNumbers']
+                        ? $this->renderLineNumberColumn('new', $newLineNum)
+                        : ''
+                ) .
+                $this->renderLineContentColumn('new', $newLine) .
+            '</tr>';
+    }
+
+    /**
+     * Renderer the line number column.
+     *
+     * @param string   $type    the diff type
+     * @param null|int $lineNum the line number
+     */
+    protected function renderLineNumberColumn(string $type, ?int $lineNum): string
+    {
+        return isset($lineNum)
+            ? '<th class="n-' . $type . '">' . $lineNum . '</th>'
+            : '<th></th>';
+    }
+
+    /**
+     * Renderer the line content column.
+     *
+     * @param string      $type    the diff type
+     * @param null|string $content the line content
+     */
+    protected function renderLineContentColumn(string $type, ?string $content): string
+    {
+        return
+            '<td class="' . $type . (isset($content) ? '' : ' none') . '">' .
+                $content .
+            '</td>';
+    }
+}
diff --git a/src/Renderer/Html/JsonHtmlWithRaw.php b/src/Renderer/Html/JsonHtmlWithRaw.php
new file mode 100644
index 0000000..5c17f64
--- /dev/null
+++ b/src/Renderer/Html/JsonHtmlWithRaw.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Jfcherng\Diff\Renderer\Html;
+
+use Jfcherng\Diff\Differ;
+use Jfcherng\Diff\Factory\LineRendererFactory;
+use Jfcherng\Diff\SequenceMatcher;
+
+/**
+ * HTML Json diff generator, with raw info.
+ */
+final class JsonHtmlWithRaw extends JsonHtml
+{
+    public function getChanges(Differ $differ): array
+    {
+        $lineRenderer = LineRendererFactory::make(
+            $this->options['detailLevel'],
+            $differ->getOptions(),
+            $this->options,
+        );
+
+        $old = $differ->getOld();
+        $new = $differ->getNew();
+	    $oldRaw = $differ->getOld();
+	    $newRaw = $differ->getNew();
+
+        $changes = [];
+
+        foreach ($differ->getGroupedOpcodes() as $hunk) {
+            $change = [];
+
+            foreach ($hunk as [$op, $i1, $i2, $j1, $j2]) {
+                $change[] = $this->getDefaultBlock($op, $i1, $j1);
+                $block = &$change[\count($change) - 1];
+
+                // if there are same amount of lines replaced
+                // we can render the inner detailed changes with corresponding lines
+                // @todo or use LineRenderer to do the job regardless different line counts?
+                if ($op === SequenceMatcher::OP_REP && $i2 - $i1 === $j2 - $j1) {
+                    for ($k = $i2 - $i1 - 1; $k >= 0; --$k) {
+                        $this->renderChangedExtent($lineRenderer, $old[$i1 + $k], $new[$j1 + $k]);
+                    }
+                }
+
+                // The next two lines are the only addition to the parent class.
+	            $block['old']['raw_lines'] = \array_slice($oldRaw, $i1, $i2 - $i1);
+	            $block['new']['raw_lines'] = \array_slice($newRaw, $j1, $j2 - $j1);
+                $block['old']['lines'] = \array_slice($old, $i1, $i2 - $i1);
+                $block['new']['lines'] = \array_slice($new, $j1, $j2 - $j1);
+            }
+            unset($block);
+
+            $changes[] = $change;
+        }
+
+        if (static::AUTO_FORMAT_CHANGES) {
+            $this->formatChanges($changes);
+        }
+
+        return $changes;
+    }
+}
diff --git a/src/Renderer/Html/DscSideBySide.php b/src/Renderer/Html/DscSideBySide.php
new file mode 100644
index 0000000..3d892dc
--- /dev/null
+++ b/src/Renderer/Html/DscSideBySide.php
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Fixes the SideBySide renderer for Deploy & Sync Content.
+ *
+ * @package EaLab\WP_Deployer
+ */
+
+declare(strict_types=1);
+
+namespace Jfcherng\Diff\Renderer\Html;
+
+use Jfcherng\Diff\SequenceMatcher;
+
+/**
+ * Side by Side HTML diff generator, with column fix for Deploy & Sync Content.
+ */
+final class DscSideBySide extends AbstractHtml
+{
+    /**
+     * {@inheritdoc}
+     */
+    public const INFO = [
+        'desc' => 'Dsc side by side',
+        'type' => 'Html',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function redererChanges(array $changes): string
+    {
+        if (empty($changes)) {
+            return $this->getResultForIdenticals();
+        }
+
+        $wrapperClasses = [
+            ...$this->options['wrapperClasses'],
+            'diff', 'diff-html', 'diff-side-by-side',
+        ];
+
+        return
+            '<table class="' . implode(' ', $wrapperClasses) . '">' .
+                $this->renderTableHeader() .
+                $this->renderTableHunks($changes) .
+            '</table>';
+    }
+
+    /**
+     * Renderer the table header.
+     */
+    protected function renderTableHeader(): string
+    {
+        if (!$this->options['showHeader']) {
+            return '';
+        }
+
+        return
+            '<thead>' .
+                '<tr>' .
+                    '<th>' . $this->_('old_version') . '</th>' .
+                    ($this->options['lineNumbers'] ? '<th></th>' : '') .
+                    '<th>' . $this->_('new_version') . '</th>' .
+                    ($this->options['lineNumbers'] ? '<th></th>' : '') .
+                '</tr>' .
+            '</thead>';
+    }
+
+    /**
+     * Renderer the table separate block.
+     */
+    protected function renderTableSeparateBlock(): string
+    {
+        $colspan = $this->options['lineNumbers'] ? '4' : '2';
+
+        return
+            '<tbody class="skipped">' .
+                '<tr>' .
+                    '<td colspan="' . $colspan . '"></td>' .
+                '</tr>' .
+            '</tbody>';
+    }
+
+    /**
+     * Renderer table hunks.
+     *
+     * @param array[][] $hunks each hunk has many blocks
+     */
+    protected function renderTableHunks(array $hunks): string
+    {
+        $ret = '';
+
+        foreach ($hunks as $i => $hunk) {
+            if ($i > 0 && $this->options['separateBlock']) {
+                $ret .= $this->renderTableSeparateBlock();
+            }
+
+            foreach ($hunk as $block) {
+                $ret .= $this->renderTableBlock($block);
+            }
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlock(array $block): string
+    {
+        switch ($block['tag']) {
+            case SequenceMatcher::OP_EQ:
+                $content = $this->renderTableBlockEqual($block);
+                break;
+            case SequenceMatcher::OP_INS:
+                $content = $this->renderTableBlockInsert($block);
+                break;
+            case SequenceMatcher::OP_DEL:
+                $content = $this->renderTableBlockDelete($block);
+                break;
+            case SequenceMatcher::OP_REP:
+                $content = $this->renderTableBlockReplace($block);
+                break;
+            default:
+                $content = '';
+        }
+
+        return '<tbody class="change change-' . self::TAG_CLASS_MAP[$block['tag']] . '">' . $content . '</tbody>';
+    }
+
+    /**
+     * Renderer the table block: equal.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockEqual(array $block): string
+    {
+        $ret = '';
+
+        $rowCount = \count($block['new']['lines']);
+
+        for ($no = 0; $no < $rowCount; ++$no) {
+            $ret .= $this->renderTableRow(
+                $block['old']['lines'][$no],
+                $block['new']['lines'][$no],
+                $block['old']['offset'] + $no + 1,
+                $block['new']['offset'] + $no + 1,
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block: insert.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockInsert(array $block): string
+    {
+        $ret = '';
+
+        foreach ($block['new']['lines'] as $no => $newLine) {
+            $ret .= $this->renderTableRow(
+                null,
+                $newLine,
+                null,
+                $block['new']['offset'] + $no + 1,
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block: delete.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockDelete(array $block): string
+    {
+        $ret = '';
+
+        foreach ($block['old']['lines'] as $no => $oldLine) {
+            $ret .= $this->renderTableRow(
+                $oldLine,
+                null,
+                $block['old']['offset'] + $no + 1,
+                null,
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer the table block: replace.
+     *
+     * @param array $block the block
+     */
+    protected function renderTableBlockReplace(array $block): string
+    {
+        $ret = '';
+
+        $lineCountMax = max(\count($block['old']['lines']), \count($block['new']['lines']));
+
+        for ($no = 0; $no < $lineCountMax; ++$no) {
+            if (isset($block['old']['lines'][$no])) {
+                $oldLineNum = $block['old']['offset'] + $no + 1;
+                $oldLine = $block['old']['lines'][$no];
+            } else {
+                $oldLineNum = $oldLine = null;
+            }
+
+            if (isset($block['new']['lines'][$no])) {
+                $newLineNum = $block['new']['offset'] + $no + 1;
+                $newLine = $block['new']['lines'][$no];
+            } else {
+                $newLineNum = $newLine = null;
+            }
+
+            $ret .= $this->renderTableRow($oldLine, $newLine, $oldLineNum, $newLineNum);
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Renderer a content row of the output table.
+     *
+     * @param null|string $oldLine    the old line
+     * @param null|string $newLine    the new line
+     * @param null|int    $oldLineNum the old line number
+     * @param null|int    $newLineNum the new line number
+     */
+    protected function renderTableRow(
+        ?string $oldLine,
+        ?string $newLine,
+        ?int $oldLineNum,
+        ?int $newLineNum
+    ): string {
+        return
+            '<tr>' .
+                (
+                    $this->options['lineNumbers']
+                        ? $this->renderLineNumberColumn('old', $oldLineNum)
+                        : ''
+                ) .
+                $this->renderLineContentColumn('old', $oldLine) .
+                (
+                    $this->options['lineNumbers']
+                        ? $this->renderLineNumberColumn('new', $newLineNum)
+                        : ''
+                ) .
+                $this->renderLineContentColumn('new', $newLine) .
+            '</tr>';
+    }
+
+    /**
+     * Renderer the line number column.
+     *
+     * @param string   $type    the diff type
+     * @param null|int $lineNum the line number
+     */
+    protected function renderLineNumberColumn(string $type, ?int $lineNum): string
+    {
+        return isset($lineNum)
+            ? '<th class="n-' . $type . '">' . $lineNum . '</th>'
+            : '<th></th>';
+    }
+
+    /**
+     * Renderer the line content column.
+     *
+     * @param string      $type    the diff type
+     * @param null|string $content the line content
+     */
+    protected function renderLineContentColumn(string $type, ?string $content): string
+    {
+        return
+            '<td class="' . $type . (isset($content) ? '' : ' none') . '">' .
+                $content .
+            '</td>';
+    }
+}
