diff --git a/checklistapi.info b/checklistapi.info
deleted file mode 100644
index a4c534b..0000000
--- a/checklistapi.info
+++ /dev/null
@@ -1,7 +0,0 @@
-name = Checklist API
-description = Provides an API for creating fillable, persistent checklists.
-core = 7.x
-package = Other
-files[] = lib/Drupal/checklistapi/ChecklistapiChecklist.php
-files[] = tests/checklistapi.test
-configure = admin/reports/checklistapi
diff --git a/checklistapi.info.yml b/checklistapi.info.yml
new file mode 100644
index 0000000..40cc413
--- /dev/null
+++ b/checklistapi.info.yml
@@ -0,0 +1,7 @@
+name: Checklist API
+description: 'Provides an API for creating fillable, persistent checklists.'
+core: 8.x
+type: module
+package: Other
+version: VERSION
+configure: admin/reports/checklistapi
diff --git a/checklistapi.module b/checklistapi.module
index c229177..9d0f588 100644
--- a/checklistapi.module
+++ b/checklistapi.module
@@ -8,6 +8,9 @@
  * completion times and users.
  */
 
+//namespace Drupal\checklistapi\ChecklistapiChecklist;
+//use Drupal\checklistapi;
+use Drupal\checklistapi\ChecklistapiChecklist;
 /**
  * Access callback: Checks the current user's access to a given checklist.
  *
@@ -60,10 +63,12 @@ function checklistapi_checklist_load($id) {
  *   exists, or an array of all checklist definitions if none is specified.
  */
 function checklistapi_get_checklist_info($id = NULL) {
-  $definitions = &drupal_static(__FUNCTION__);
-  if (!isset($definitions)) {
-    // Get definitions.
-    $definitions = module_invoke_all('checklistapi_checklist_info');
+  static $drupal_static_definitions;
+  if (!isset($drupal_static_definitions)) {
+    $drupal_static_definitions['definitions'] = &drupal_static(__FUNCTION__);
+  }
+  if (!isset($drupal_static_definitions['definitions'])) {
+    $definitions = \Drupal::moduleHandler()->invokeAll('checklistapi_checklist_info');
     $definitions = checklistapi_sort_array($definitions);
     // Let other modules alter them.
     drupal_alter('checklistapi_checklist_info', $definitions);
@@ -91,9 +96,9 @@ function checklistapi_help($path, $arg) {
 }
 
 /**
- * Implements hook_init().
+ * Implements hook_page_build().
  */
-function checklistapi_init() {
+function checklistapi_page_build(&$page) {
   // Disable page caching on all Checklist API module paths.
   $module_paths = array_keys(checklistapi_menu());
   if (in_array(current_path(), $module_paths)) {
@@ -109,53 +114,33 @@ function checklistapi_menu() {
 
   // Checklists report.
   $items['admin/reports/checklistapi'] = array(
-    'title' => 'Checklists',
-    'page callback' => 'checklistapi_report_form',
-    'access arguments' => array('view checklistapi checklists report'),
+    'title'       => 'Checklists',
     'description' => 'Get an overview of your installed checklists with progress details.',
-    'file' => 'checklistapi.admin.inc',
+    'route_name'  => 'checklistapi.report',
   );
 
   // Individual checklists.
-  foreach (checklistapi_get_checklist_info() as $id => $definition) {
+  foreach (checklistapi_get_checklist_info() as $plugin_id => $definition) {
     if (empty($definition['#path']) || empty($definition['#title'])) {
       continue;
     }
-
-    // View/edit checklist.
     $items[$definition['#path']] = array(
-      'title' => $definition['#title'],
+      'title'       => $definition['#title'],
       'description' => (!empty($definition['#description'])) ? $definition['#description'] : '',
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array('checklistapi_checklist_form', $id),
-      'access callback' => 'checklistapi_checklist_access',
-      'access arguments' => array($id),
-      'file' => 'checklistapi.pages.inc',
+      'route_name'  => 'checklistapi.definition_' . $plugin_id,
+    );
+    $items[$definition['#path'] . '/compact'] = array(
+      'title'      => 'Compact mode',
+      'route_name' => 'checklistapi.definition_' . $plugin_id . '_compact',
     );
-    if (!empty($definition['#menu_name'])) {
-      $items[$definition['#path']]['menu_name'] = $definition['#menu_name'];
-    }
-
-    // Clear saved progress.
     $items[$definition['#path'] . '/clear'] = array(
-      'title' => 'Clear',
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array('checklistapi_checklist_clear_confirm', $id),
-      'access callback' => 'checklistapi_checklist_access',
-      'access arguments' => array($id, 'edit'),
-      'file' => 'checklistapi.pages.inc',
-      'type' => MENU_CALLBACK,
+      'title'      => 'Clear',
+      'route_name' => 'checklistapi.definition_' . $plugin_id . '_clear',
     );
 
-    // Toggle compact mode.
-    $items[$definition['#path'] . '/compact'] = array(
-      'title' => 'Compact mode',
-      'page callback' => 'checklistapi_compact_page',
-      'access callback' => 'checklistapi_checklist_access',
-      'access arguments' => array($id),
-      'file' => 'checklistapi.pages.inc',
-      'type' => MENU_CALLBACK,
-    );
+    if (!empty($definition['#menu_name'])) {
+      $items[$definition['#path']]['menu_name'] = $definition['#menu_name'];
+    }
   }
 
   return $items;
diff --git a/checklistapi.pages.inc b/checklistapi.pages.inc
index 9b832f3..41ad5a9 100644
--- a/checklistapi.pages.inc
+++ b/checklistapi.pages.inc
@@ -6,191 +6,6 @@
  */
 
 /**
- * Page callback: Form constructor for "Clear saved progress" confirmation form.
- *
- * @param string $id
- *   The checklist ID.
- *
- * @see checklistapi_menu()
- *
- * @ingroup forms
- */
-function checklistapi_checklist_clear_confirm($form, &$form_state, $id) {
-  $checklist = checklistapi_checklist_load($id);
-  $form['#checklist'] = $checklist;
-  $question = t('Are you sure you want to clear %title saved progress?', array(
-    '%title' => $checklist->title,
-  ));
-  $description = t('All progress details will be erased. This action cannot be undone.');
-  $yes = t('Clear');
-  $no = t('Cancel');
-  return confirm_form($form, $question, $checklist->path, $description, $yes, $no);
-}
-
-/**
- * Form submission handler for checklistapi_checklist_clear_confirm().
- */
-function checklistapi_checklist_clear_confirm_submit($form, &$form_state) {
-  // If user confirmed, clear saved progress.
-  if ($form_state['values']['confirm']) {
-    $form['#checklist']->clearSavedProgress();
-  }
-
-  // Redirect back to checklist.
-  $form_state['redirect'] = $form['#checklist']->path;
-}
-
-/**
- * Page callback: Form constructor for the checklist form.
- *
- * @param string $id
- *   The checklist ID.
- *
- * @see checklistapi_checklist_form_submit()
- * @see checklistapi_menu()
- *
- * @ingroup forms
- */
-function checklistapi_checklist_form($form, &$form_state, $id) {
-  $form['#checklist'] = $checklist = checklistapi_checklist_load($id);
-
-  $form['progress_bar'] = array(
-    '#type' => 'markup',
-    '#markup' => theme('checklistapi_progress_bar', array(
-      'message' => ($checklist->hasSavedProgress()) ? t('Last updated @date by !user', array(
-        '@date' => $checklist->getLastUpdatedDate(),
-        '!user' => $checklist->getLastUpdatedUser(),
-      )) : '&nbsp;',
-      'number_complete' => $checklist->getNumberCompleted(),
-      'number_of_items' => $checklist->getNumberOfItems(),
-      'percent_complete' => round($checklist->getPercentComplete()),
-    )),
-  );
-  if (checklistapi_compact_mode()) {
-    $form['#attributes']['class'] = array('compact-mode');
-  }
-  $form['compact_mode_link'] = array(
-    '#markup' => theme('checklistapi_compact_link'),
-  );
-
-  $form['checklistapi'] = array(
-    '#attached' => array(
-      'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
-      'js' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
-    ),
-    '#tree' => TRUE,
-    '#type' => 'vertical_tabs',
-  );
-
-  // Loop through groups.
-  $num_autochecked_items = 0;
-  $groups = $checklist->items;
-  foreach (element_children($groups) as $group_key) {
-    $group = &$groups[$group_key];
-    $form['checklistapi'][$group_key] = array(
-      '#title' => filter_xss($group['#title']),
-      '#type' => 'fieldset',
-    );
-    if (!empty($group['#description'])) {
-      $form['checklistapi'][$group_key]['#description'] = filter_xss_admin($group['#description']);
-    }
-
-    // Loop through items.
-    foreach (element_children($group) as $item_key) {
-      $item = &$group[$item_key];
-      $saved_item = !empty($checklist->savedProgress[$item_key]) ? $checklist->savedProgress[$item_key] : 0;
-      // Build title.
-      $title = filter_xss($item['#title']);
-      if ($saved_item) {
-        // Append completion details.
-        $user = user_load($saved_item['#uid']);
-        $title .= t(
-          '<span class="completion-details"> - Completed @time by !user</a>',
-          array(
-            '@time' => format_date($saved_item['#completed'], 'short'),
-            '!user' => theme('username', array('account' => $user)),
-          )
-        );
-      }
-      // Set default value.
-      $default_value = FALSE;
-      if ($saved_item) {
-        $default_value = TRUE;
-      }
-      elseif (!empty($item['#default_value'])) {
-        if ($default_value = $item['#default_value']) {
-          $num_autochecked_items++;
-        }
-      }
-      // Get description.
-      $description = (isset($item['#description'])) ? '<p>' . filter_xss_admin($item['#description']) . '</p>' : '';
-      // Append links.
-      $links = array();
-      foreach (element_children($item) as $link_key) {
-        $link = &$item[$link_key];
-        $options = (!empty($link['#options']) && is_array($link['#options'])) ? $link['#options'] : array();
-        $links[] = l($link['#text'], $link['#path'], $options);
-      }
-      if (count($links)) {
-        $description .= '<div class="links">' . implode(' | ', $links) . '</div>';
-      }
-      // Compile the list item.
-      $form['checklistapi'][$group_key][$item_key] = array(
-        '#attributes' => array('class' => array('checklistapi-item')),
-        '#default_value' => $default_value,
-        '#description' => filter_xss_admin($description),
-        '#disabled' => !($user_has_edit_access = $checklist->userHasAccess('edit')),
-        '#title' => filter_xss_admin($title),
-        '#type' => 'checkbox',
-      );
-    }
-  }
-
-  $form['actions'] = array(
-    '#access' => $user_has_edit_access,
-    '#type' => 'actions',
-    '#weight' => 100,
-    'save' => array(
-      '#submit' => array('checklistapi_checklist_form_submit'),
-      '#type' => 'submit',
-      '#value' => t('Save'),
-    ),
-    'clear' => array(
-      '#access' => $checklist->hasSavedProgress(),
-      '#attributes' => array('class' => array('clear-saved-progress')),
-      '#href' => $checklist->path . '/clear',
-      '#title' => t('Clear saved progress'),
-      '#type' => 'link',
-    ),
-  );
-
-  // Alert the user of autochecked items. Only set the message on GET requests
-  // to prevent it from reappearing after saving the form. (Testing the request
-  // method may not be the "correct" way to accomplish this.)
-  if ($num_autochecked_items && $_SERVER['REQUEST_METHOD'] == 'GET') {
-    $args = array(
-      '%checklist' => $checklist->title,
-      '@num' => $num_autochecked_items,
-    );
-    $message = format_plural(
-      $num_autochecked_items,
-      t('%checklist found 1 unchecked item that was already completed and checked it for you. Save the form to record the change.', $args),
-      t('%checklist found @num unchecked items that were already completed and checked them for you. Save the form to record the changes.', $args)
-    );
-    drupal_set_message($message, 'status');
-  }
-
-  return $form;
-}
-
-/**
- * Form submission handler for checklistapi_checklist_form().
- */
-function checklistapi_checklist_form_submit($form, &$form_state) {
-  $form['#checklist']->saveProgress($form_state['values']['checklistapi']);
-}
-
-/**
  * Determines whether the current user is in compact mode.
  *
  * Compact mode shows checklist forms with less description text.
@@ -211,18 +26,6 @@ function checklistapi_compact_mode() {
 }
 
 /**
- * Menu callback: Sets whether the admin menu is in compact mode or not.
- *
- * @param string $mode
- *   (optional) The mode to set compact mode to. Accepted values are "on" and
- *   "off". Defaults to "off".
- */
-function checklistapi_compact_page($mode = 'off') {
-  user_cookie_save(array('checklistapi_compact_mode' => ($mode == 'on')));
-  drupal_goto();
-}
-
-/**
  * Returns HTML for a link to show or hide inline item descriptions.
  *
  * @ingroup themeable
diff --git a/checklistapi.routing.yml b/checklistapi.routing.yml
new file mode 100644
index 0000000..ab58132
--- /dev/null
+++ b/checklistapi.routing.yml
@@ -0,0 +1,10 @@
+checklistapi.report:
+  path: '/admin/reports/checklistapi'
+  defaults:
+    _title: 'Checklists'
+    _content: '\Drupal\checklistapi\Controller\ChecklistapiReportController::simple'
+  requirements:
+    _permission: 'view checklistapi checklists report'
+
+route_callbacks:
+  - '\Drupal\checklistapi\Routing\checklistapiRouteSubscriber::routes'
diff --git a/checklistapi.services.yml b/checklistapi.services.yml
new file mode 100644
index 0000000..a4cb17e
--- /dev/null
+++ b/checklistapi.services.yml
@@ -0,0 +1,5 @@
+services:
+  checklistapi.access_check:
+    class: Drupal\checklistapi\Access\checklistapiAccessCheck
+    tags:
+      - { name: access_check, applies_to: _checklistapi_access }
diff --git a/checklistapi_example/checklistapi_example.info b/checklistapi_example/checklistapi_example.info
deleted file mode 100644
index 9d085dc..0000000
--- a/checklistapi_example/checklistapi_example.info
+++ /dev/null
@@ -1,6 +0,0 @@
-name = Checklist API example
-description = Provides an example implementation of the Checklist API.
-core = 7.x
-package = Example modules
-dependencies[] = checklistapi
-configure = admin/config/development/checklistapi-example
diff --git a/checklistapi_example/checklistapi_example.info.yml b/checklistapi_example/checklistapi_example.info.yml
new file mode 100644
index 0000000..89fb6d7
--- /dev/null
+++ b/checklistapi_example/checklistapi_example.info.yml
@@ -0,0 +1,9 @@
+name: Checklist API example
+type: module
+description: 'Provides an example implementation of the Checklist API.'
+package: Example modules
+version: VERSION
+core: 8.x
+configure: admin/config/development/checklistapi-example
+dependencies:
+  - checklistapi
diff --git a/lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php b/lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php
new file mode 100644
index 0000000..dd3569e
--- /dev/null
+++ b/lib/Drupal/checklistapi/Access/checklistapiAccessCheck.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\checklistapi\Access;
+
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+class checklistapiAccessCheck implements AccessInterface {
+
+  /**
+   * The key used by the routing requirement.
+   *
+   * @var string
+   */
+  protected $requirementsKey = '_checklistapi_access';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request, AccountInterface $account) {
+    $op = $request->attributes->get('op');
+    $op = !empty($op) ? $op : 'any';
+
+    return checklistapi_checklist_access($request->attributes->get('plugin_id'), $op) ? static::ALLOW : static::DENY;
+  }
+}
diff --git a/lib/Drupal/checklistapi/ChecklistapiChecklist.php b/lib/Drupal/checklistapi/ChecklistapiChecklist.php
index 6a06b33..a91a1cd 100644
--- a/lib/Drupal/checklistapi/ChecklistapiChecklist.php
+++ b/lib/Drupal/checklistapi/ChecklistapiChecklist.php
@@ -5,6 +5,8 @@
  * Class for Checklist API checklists.
  */
 
+namespace Drupal\checklistapi;
+
 /**
  * Defines the checklist class.
  */
@@ -201,12 +203,13 @@ class ChecklistapiChecklist {
    * @see checklistapi_checklist_form_submit()
    */
   public function saveProgress(array $values) {
-    global $user;
+    $account = \Drupal::currentUser();
+
     $time = time();
     $num_changed_items = 0;
     $progress = array(
       '#changed' => $time,
-      '#changed_by' => $user->uid,
+      '#changed_by' => $account->id(),
       '#completed_items' => 0,
     );
 
diff --git a/lib/Drupal/checklistapi/Controller/ChecklistapiCompactPageController.php b/lib/Drupal/checklistapi/Controller/ChecklistapiCompactPageController.php
new file mode 100644
index 0000000..e66f022
--- /dev/null
+++ b/lib/Drupal/checklistapi/Controller/ChecklistapiCompactPageController.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Controller\ChecklistapiReportController.
+ */
+
+namespace Drupal\checklistapi\Controller;
+
+// use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Controller routines for page example routes.
+ */
+class ChecklistapiCompactPageController {
+
+  /**
+   * Constructs a simple page.
+   */
+  function simple() {
+    user_cookie_save(array('checklistapi_compact_mode' => ($mode == 'on')));
+    drupal_goto();
+  }
+}
diff --git a/lib/Drupal/checklistapi/Controller/ChecklistapiReportController.php b/lib/Drupal/checklistapi/Controller/ChecklistapiReportController.php
new file mode 100644
index 0000000..76a2a84
--- /dev/null
+++ b/lib/Drupal/checklistapi/Controller/ChecklistapiReportController.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Controller\ChecklistapiReportController.
+ */
+
+namespace Drupal\checklistapi\Controller;
+
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Controller routines for page example routes.
+ */
+class ChecklistapiReportController {
+
+  /**
+   * Constructs a page with descriptive content.
+   */
+  function description() {
+    $build = array(
+      '#markup' => t('<p>The Page example module provides two pages, "simple" and "arguments".</p><p>The <a href="@simple_link">simple page</a> just returns a renderable array for display.</p><p>The <a href="@arguments_link">arguments page</a> takes two arguments and displays them, as in @arguments_link</p>', array('@simple_link' => url('examples/page_example/simple', array('absolute' => TRUE)), '@arguments_link' => url('examples/page_example/arguments/23/56', array('absolute' => TRUE))))
+    );
+
+    return $build;
+  }
+
+  /**
+   * Constructs a simple page.
+   */
+  function simple() {
+    // Define table header.
+    $header = array(
+      t('Checklist'),
+      t('Progress'),
+      t('Last updated'),
+      t('Last updated by'),
+      t('Operations'),
+    );
+
+    // Build table rows.
+    $rows = array();
+    $definitions = checklistapi_get_checklist_info();
+
+    foreach ($definitions as $id => $definition) {
+      $checklist = checklistapi_checklist_load($id);
+      $row = array();
+      $row[] = array(
+        'data' => ($checklist->userHasAccess()) ? l($checklist->title, $checklist->path) : drupal_placeholder($checklist->title),
+        'title' => (!empty($checklist->description)) ? $checklist->description : '',
+      );
+      $row[] = t('@completed of @total (@percent%)', array(
+        '@completed' => $checklist->getNumberCompleted(),
+        '@total' => $checklist->getNumberOfItems(),
+        '@percent' => round($checklist->getPercentComplete()),
+      ));
+      $row[] = $checklist->getLastUpdatedDate();
+      $row[] = $checklist->getLastUpdatedUser();
+      $row[] = ($checklist->userHasAccess('edit') && $checklist->hasSavedProgress()) ? l(t('clear saved progress'), $checklist->path . '/clear', array(
+        'query' => array('destination' => 'admin/reports/checklistapi'),
+      )) : '';
+      $rows[] = $row;
+    }
+
+    // Compile table.
+    $table = array(
+      'header' => $header,
+      'rows' => $rows,
+      'empty' => t('No checklists available.'),
+    );
+
+    return theme('table', $table);
+  }
+
+  /**
+   * A more complex _content callback that takes arguments.
+   *
+   * This callback is mapped to the path
+   * 'examples/page_example/arguments/{first}/{second}'.
+   *
+   * The arguments in brackets are passed to this callback from the page URL.
+   * The placeholder names "first" and "second" can have any value but should
+   * match the callback method variable names; i.e. $first and $second.
+   *
+   * This function also demonstrates a more complex render array in the returned
+   * values. Instead of rendering the HTML with theme('item_list'), content is
+   * left un-rendered, and the theme function name is set using #theme. This
+   * content will now be rendered as late as possible, giving more parts of the
+   * system a chance to change it if necessary.
+   *
+   * Consult @link http://drupal.org/node/930760 Render Arrays documentation
+   * @endlink for details.
+   *
+   * @param string $first
+   *   A string to use, should be a number.
+   * @param string $second
+   *   Another string to use, should be a number.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   If the parameters are invalid.
+   */
+  function arguments($first, $second) {
+    // Make sure you don't trust the URL to be safe! Always check for exploits.
+    if (!is_numeric($first) || !is_numeric($second)) {
+      // We will just show a standard "access denied" page in this case.
+      throw new AccessDeniedHttpException();
+    }
+
+    $list[] = t("First number was @number.", array('@number' => $first));
+    $list[] = t("Second number was @number.", array('@number' => $second));
+    $list[] = t('The total was @number.', array('@number' => $first + $second));
+
+    $render_array['page_example_arguments'] = array(
+      // The theme function to apply to the #items
+      '#theme' => 'item_list',
+      // The list itself.
+      '#items' => $list,
+      '#title' => t('Argument Information'),
+    );
+    return $render_array;
+  }
+
+}
diff --git a/lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php b/lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php
new file mode 100644
index 0000000..a93123a
--- /dev/null
+++ b/lib/Drupal/checklistapi/Form/ChecklistapiChecklistClearConfirmForm.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Form\ChecklistapiDefinitionForm.
+ */
+
+namespace Drupal\checklistapi\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+
+/**
+ * todo
+ */
+class ChecklistapiChecklistClearConfirmForm extends ConfirmFormBase {
+  /**
+   * The ID of the item to delete.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * Implements \Drupal\Core\Form\ConfirmFormBase::getQuestion().
+   */
+  public function getQuestion() {
+    // $checklist = checklistapi_checklist_load($plugin_id);
+    return t('Are you sure you want to clear saved progress?');
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\ConfirmFormBase::getCancelRoute().
+   */
+  public function getCancelRoute() {
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::getDescription().
+   */
+  public function getDescription() {
+    return t('All progress details will be erased. This action cannot be undone.');
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::getConfirmText().
+   */
+  public function getConfirmText() {
+    return t('Clear');
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::getCancelText().
+   */
+  public function getCancelText() {
+    return t('Cancel');
+  }
+
+  /**
+   * Overrides \Drupal\Core\Form\ConfirmFormBase::buildForm().
+   *
+   * @param int $id
+   *   (optional) The ID of the item to be deleted.
+   */
+  public function buildForm(array $form, array &$form_state, $plugin_id = NULL) {
+    $checklist = checklistapi_checklist_load($plugin_id);
+    $form['#checklist'] = $checklist;
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::submitForm().
+   */
+  public function submitForm(array &$form, array &$form_state) {
+     // If user confirmed, clear saved progress.
+    if ($form_state['values']['confirm']) {
+      $form['#checklist']->clearSavedProgress();
+    }
+
+    // Redirect back to checklist.
+    $form_state['redirect'] = $form['#checklist']->path;
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'checklistapi_checklist_clear_confirm';
+  }
+
+}
diff --git a/lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php b/lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php
new file mode 100644
index 0000000..090660f
--- /dev/null
+++ b/lib/Drupal/checklistapi/Form/ChecklistapiDefinitionForm.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Form\ChecklistapiDefinitionForm.
+ */
+
+namespace Drupal\checklistapi\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+
+/**
+ * todo
+ */
+class ChecklistapiDefinitionForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'checklistapi_checklist_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, $plugin_id = NULL) {
+    module_load_include('inc', 'checklistapi', 'checklistapi.pages');
+    $form['#checklist'] = $checklist = checklistapi_checklist_load($plugin_id);
+
+    $form['progress_bar'] = array(
+      '#type' => 'markup',
+      '#markup' => theme('checklistapi_progress_bar', array(
+        'message' => ($checklist->hasSavedProgress()) ? t('Last updated @date by !user', array(
+          '@date' => $checklist->getLastUpdatedDate(),
+          '!user' => $checklist->getLastUpdatedUser(),
+        )) : '&nbsp;',
+        'number_complete' => $checklist->getNumberCompleted(),
+        'number_of_items' => $checklist->getNumberOfItems(),
+        'percent_complete' => round($checklist->getPercentComplete()),
+      )),
+    );
+    if (checklistapi_compact_mode()) {
+      $form['#attributes']['class'] = array('compact-mode');
+    }
+    $form['compact_mode_link'] = array(
+      '#markup' => theme('checklistapi_compact_link'),
+    );
+    $form['checklistapi'] = array(
+      '#attached' => array(
+        'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
+        'js'  => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
+      ),
+      '#tree' => TRUE,
+      '#type' => 'vertical_tabs',
+    );
+
+    // Loop through groups.
+    $num_autochecked_items = 0;
+    $groups = $checklist->items;
+    foreach (element_children($groups) as $group_key) {
+      $group = &$groups[$group_key];
+      $form['tabs'][$group_key] = array(
+        '#title' => filter_xss($group['#title']),
+        '#type' => 'details',
+        '#group' => 'checklistapi',
+      );
+      $test = $form['tabs'][$group_key];
+      if (!empty($group['#description'])) {
+        $form['tabs'][$group_key]['#description'] = filter_xss_admin($group['#description']);
+      }
+
+      // Loop through items.
+      foreach (element_children($group) as $item_key) {
+        $item = &$group[$item_key];
+        $saved_item = !empty($checklist->savedProgress[$item_key]) ? $checklist->savedProgress[$item_key] : 0;
+        // Build title.
+        $title = filter_xss($item['#title']);
+        if ($saved_item) {
+          // Append completion details.
+          $user = user_load($saved_item['#uid']);
+          $title .= t(
+            '<span class="completion-details"> - Completed @time by !user</a>',
+            array(
+              '@time' => format_date($saved_item['#completed'], 'short'),
+              '!user' => theme('username', array('account' => $user)),
+            )
+          );
+        }
+        // Set default value.
+        $default_value = FALSE;
+        if ($saved_item) {
+          $default_value = TRUE;
+        }
+        elseif (!empty($item['#default_value'])) {
+          if ($default_value = $item['#default_value']) {
+            $num_autochecked_items++;
+          }
+        }
+        // Get description.
+        $description = (isset($item['#description'])) ? '<p>' . filter_xss_admin($item['#description']) . '</p>' : '';
+        // Append links.
+        $links = array();
+        foreach (element_children($item) as $link_key) {
+          $link = &$item[$link_key];
+          $options = (!empty($link['#options']) && is_array($link['#options'])) ? $link['#options'] : array();
+          $links[] = l($link['#text'], $link['#path'], $options);
+        }
+        if (count($links)) {
+          $description .= '<div class="links">' . implode(' | ', $links) . '</div>';
+        }
+        // Compile the list item.
+        $form['tabs'][$group_key][$item_key] = array(
+          '#attributes' => array('class' => array('checklistapi-item')),
+          '#default_value' => $default_value,
+          '#description' => filter_xss_admin($description),
+          '#disabled' => !($user_has_edit_access = $checklist->userHasAccess('edit')),
+          '#title' => filter_xss_admin($title),
+          '#type' => 'checkbox',
+        );
+      }
+    }
+
+    $form['actions'] = array(
+      '#access' => $user_has_edit_access,
+      '#type' => 'actions',
+      '#weight' => 100,
+      'save' => array(
+        '#submit' => array('checklistapi_checklist_form_submit'),
+        '#type' => 'submit',
+        '#value' => t('Save'),
+      ),
+      'clear' => array(
+        '#access' => $checklist->hasSavedProgress(),
+        '#attributes' => array('class' => array('clear-saved-progress')),
+        '#href' => $checklist->path . '/clear',
+        '#title' => t('Clear saved progress'),
+        '#type' => 'link',
+      ),
+    );
+
+    // Alert the user of autochecked items. Only set the message on GET requests
+    // to prevent it from reappearing after saving the form. (Testing the request
+    // method may not be the "correct" way to accomplish this.)
+    if ($num_autochecked_items && $_SERVER['REQUEST_METHOD'] == 'GET') {
+      $args = array(
+        '%checklist' => $checklist->title,
+        '@num' => $num_autochecked_items,
+      );
+      $message = format_plural(
+        $num_autochecked_items,
+        t('%checklist found 1 unchecked item that was already completed and checked it for you. Save the form to record the change.', $args),
+        t('%checklist found @num unchecked items that were already completed and checked them for you. Save the form to record the changes.', $args)
+      );
+      drupal_set_message($message, 'status');
+    }
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    parent::validateForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $form['#checklist']->saveProgress($form_state['values']['checklistapi']);
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/lib/Drupal/checklistapi/Routing/checklistapiRouteSubscriber.php b/lib/Drupal/checklistapi/Routing/checklistapiRouteSubscriber.php
new file mode 100644
index 0000000..b1e6d28
--- /dev/null
+++ b/lib/Drupal/checklistapi/Routing/checklistapiRouteSubscriber.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\checklistapi\Routing\checklistapiRouteSubscriber.
+ */
+
+namespace Drupal\checklistapi\Routing;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Listens to the dynamic trousers route events.
+ */
+class checklistapiRouteSubscriber {
+
+  /**
+   * Provides dynamic routes for various checklistapi pages.
+   */
+  public function routes() {
+    $routes = array();
+    foreach (checklistapi_get_checklist_info() as $id => $definition) {
+      if (empty($definition['#path']) || empty($definition['#title'])) {
+        continue;
+      }
+      $routes['checklistapi.definition_' . $id] = new Route(
+        $definition['#path'],
+        array(
+          '_title'    => $definition['#title'],
+          '_form'     => '\Drupal\checklistapi\Form\ChecklistapiDefinitionForm',
+          'plugin_id' => $id,
+        ),
+        array('_checklistapi_access' => 'TRUE')
+      );
+      $routes['checklistapi.definition_' . $id . '_compact'] = new Route(
+        $definition['#path'] . '/compact',
+        array(
+          '_title'    => 'Compact mode',
+          '_content'  => '\Drupal\checklistapi\Controller\ChecklistapiCompactPageController::simple',
+          'plugin_id' => $id,
+        ),
+        array('_checklistapi_access' => 'TRUE')
+      );
+      $routes['checklistapi.definition_' . $id . '_clear'] = new Route(
+        $definition['#path'] . '/clear',
+        array(
+          '_title'    => 'Clear',
+          '_form'     => '\Drupal\checklistapi\Form\ChecklistapiChecklistClearConfirmForm',
+          'plugin_id' => $id,
+          'op'        => 'edit',
+        ),
+        array('_checklistapi_access' => 'TRUE')
+      );
+
+      return $routes;
+    }
+  }
+}
diff --git a/templates/checklistapi-progress-bar.html.twig b/templates/checklistapi-progress-bar.html.twig
new file mode 100644
index 0000000..daaa12b
--- /dev/null
+++ b/templates/checklistapi-progress-bar.html.twig
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation for the Checklist API progress bar.
+ *
+ * Available variables:
+ * - $message: The progress message.
+ * - $number_complete: The number of items complete.
+ * - $number_of_items: The total number of items.
+ * - $percent_complete: The percent of items complete.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_checklistapi_progress_bar()
+ * @see template_process()
+ */
+?>
+<div class="progress">
+  <div class="bar"><div class="filled" style="width: {{ percent_complete }}%;"></div></div>
+  <div class="percentage">{{ number_complete }} of {{ number_of_items }} ({{ percent_complete }}%)</div>
+  <div class="message">{{ message }}</div>
+</div>
diff --git a/templates/checklistapi-progress-bar.tpl.php b/templates/checklistapi-progress-bar.tpl.php
deleted file mode 100644
index 383dbf8..0000000
--- a/templates/checklistapi-progress-bar.tpl.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * @file
- * Default theme implementation for the Checklist API progress bar.
- *
- * Available variables:
- * - $message: The progress message.
- * - $number_complete: The number of items complete.
- * - $number_of_items: The total number of items.
- * - $percent_complete: The percent of items complete.
- *
- * @see template_preprocess()
- * @see template_preprocess_checklistapi_progress_bar()
- * @see template_process()
- */
-?>
-<div class="progress">
-  <div class="bar"><div class="filled" style="width:<?php print $percent_complete; ?>%;"></div></div>
-  <div class="percentage"><?php print $number_complete; ?> of <?php print $number_of_items; ?> (<?php print $percent_complete; ?>%)</div>
-  <div class="message"><?php print $message; ?></div>
-</div>
