diff --git a/core/modules/user/templates/user-permission-description.html.twig b/core/modules/user/templates/user-permission-description.html.twig
new file mode 100644
index 0000000..d1d5562
--- /dev/null
+++ b/core/modules/user/templates/user-permission-description.html.twig
@@ -0,0 +1,26 @@
+{#
+/**
+ * @file
+ * Default theme implementation for an individual permission description.
+ *
+ * Available variables:
+ * - description: The text of the permission description.
+ * - warning: A security-related warning message about the permission (if
+ *   there is one).
+ * - hide: Indicates whether or not the permission description was requested
+ *   to be hidden rather than shown.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_user_permission_description()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if not hide %}
+  {% if description  %}
+    {{- description -}}
+  {% endif %}
+  {% if warning %}
+    <em class="permission-warning">{{ warning }}</em>
+  {% endif %}
+{% endif %}
diff --git a/core/modules/user/templates/user-picture.tpl.php b/core/modules/user/templates/user-picture.tpl.php
deleted file mode 100644
index ee82187..0000000
--- a/core/modules/user/templates/user-picture.tpl.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-/**
- * @file
- * Default theme implementation to present a picture configured for the
- * user's account.
- *
- * Available variables:
- * - $user_picture: Image set by the user or the site's default. Will be linked
- *   depending on the viewer's permission to view the user's profile page.
- * - $account: Array of account information. Potentially unsafe. Be sure to
- *   check_plain() before use.
- *
- * @see template_preprocess_user_picture()
- *
- * @ingroup themeable
- */
-?>
-<?php if ($user_picture): ?>
-  <div class="user-picture">
-    <?php print $user_picture; ?>
-  </div>
-<?php endif; ?>
diff --git a/core/modules/user/templates/user-signature.html.twig b/core/modules/user/templates/user-signature.html.twig
new file mode 100644
index 0000000..9f490ef
--- /dev/null
+++ b/core/modules/user/templates/user-signature.html.twig
@@ -0,0 +1,18 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a user signature.
+ *
+ * Available variables:
+ * - signature: The user's signature.
+ * - attributes: An array of HTML attributes for the wrapper of the signature.
+ *
+ * @TODO Use a general wrapper instead of this file.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_user_signature()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>{{ signature }}</div>
diff --git a/core/modules/user/templates/user.html.twig b/core/modules/user/templates/user.html.twig
new file mode 100644
index 0000000..d8b8242
--- /dev/null
+++ b/core/modules/user/templates/user.html.twig
@@ -0,0 +1,34 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present all user data.
+ *
+ * This template is used when viewing a registered user's page,
+ * e.g., example.com/user/123. 123 being the users ID.
+ *
+ * Use {{ content }} to print all content, or print a subset
+ * such as {{ content.field_example }}.
+ * By default, $user_profile['summary'] is provided, which contains data on the
+ * user's history. Other data can be included by modules.
+ *
+ * Available variables:
+ *   - content: A list of content items.
+ *   - Field variables: for each field instance attached to the user a
+ *     corresponding variable is defined; e.g., account.field_example has a
+ *     variable 'field_example' defined. When needing to access a field's raw
+ *     values, developers/themers are strongly encouraged to use these
+ *     variables. Otherwise they will have to explicitly specify the desired
+ *     field language, e.g. account.field_example['en'], thus overriding any
+ *     language negotiation rule that was previously applied.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_user()
+ *
+ * @ingroup themeable
+ */
+#}
+<article{{ attributes }}>
+  {% if content is not empty %}
+    {{- content -}}
+  {% endif %}
+</article>
diff --git a/core/modules/user/templates/user.tpl.php b/core/modules/user/templates/user.tpl.php
deleted file mode 100644
index 617d310..0000000
--- a/core/modules/user/templates/user.tpl.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/**
- * @file
- * Default theme implementation to present all user data.
- *
- * This template is used when viewing a registered user's page,
- * e.g., example.com/user/123. 123 being the users ID.
- *
- * Use render($content) to print all content, or print a subset
- * such as render($content['field_example']).
- * By default, $user_profile['summary'] is provided, which contains data on the
- * user's history. Other data can be included by modules.
- *
- * Available variables:
- *   - $content: An array of content items. Use render() to print them.
- *   - Field variables: for each field instance attached to the user a
- *     corresponding variable is defined; e.g., $account->field_example has a
- *     variable $field_example defined. When needing to access a field's raw
- *     values, developers/themers are strongly encouraged to use these
- *     variables. Otherwise they will have to explicitly specify the desired
- *     field language, e.g. $account->field_example['en'], thus overriding any
- *     language negotiation rule that was previously applied.
- *
- * @see template_preprocess_user()
- *
- * @ingroup themeable
- */
-?>
-<article class="profile"<?php print $attributes; ?>>
-  <?php print render($content); ?>
-</article>
diff --git a/core/modules/user/templates/username.html.twig b/core/modules/user/templates/username.html.twig
new file mode 100644
index 0000000..4524dba
--- /dev/null
+++ b/core/modules/user/templates/username.html.twig
@@ -0,0 +1,32 @@
+{#
+/**
+ * @file
+ * Default theme implementation for displaying a username.
+ *
+ * Available variables:
+ * - account: The user object to format.
+ * - name: The user's name, sanitized.
+ * - extra: Additional text to append to the user's name, sanitized.
+ * - link: The fully generated link by l if a link_path is set
+ * - link_path: The path or URL of the user's profile page, home page,
+ *   or other desired page to link to for more information about the user.
+ * - link_options: An array of options to pass to the l() function's $options
+ *   parameter if linking the user's name to the user's page.
+ * - attributes: An array of attributes if not linking to the user's page.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_username()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if link %}
+  {{- link -}}
+{% else %}
+  {#
+    Modules may have added important attributes so they must be included
+    in the output. Additional classes may be added as array elements like
+    {% set attributes.class = attributes.class|merge(["myclass"]) %}
+  #}
+  <span{{ attributes }}>{{ name }}{{ extra }}</span>
+{% endif %}
diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index c3931d8..aee6ea1 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -818,9 +818,11 @@ function theme_user_admin_permissions($variables) {
 }
 
 /**
- * Returns HTML for an individual permission description.
+ * Default theme implementation for an individual permission description.
  *
- * @param $variables
+ * Default template: user-permission-description.html.twig.
+ *
+ * @param array $variables
  *   An associative array containing:
  *   - permission_item: An associative array representing the permission whose
  *     description is being themed. Useful keys include:
@@ -829,22 +831,14 @@ function theme_user_admin_permissions($variables) {
  *       there is one).
  *   - hide: A boolean indicating whether or not the permission description was
  *     requested to be hidden rather than shown.
- *
- * @ingroup themeable
  */
-function theme_user_permission_description($variables) {
-  if (!$variables['hide']) {
-    $description = array();
-    $permission_item = $variables['permission_item'];
-    if (!empty($permission_item['description'])) {
-      $description[] = $permission_item['description'];
-    }
-    if (!empty($permission_item['warning'])) {
-      $description[] = '<em class="permission-warning">' . $permission_item['warning'] . '</em>';
-    }
-    if (!empty($description)) {
-      return implode(' ', $description);
-    }
+function template_preprocess_user_permission_description(&$variables) {
+  $permission_item = $variables['permission_item'];
+  if (!empty($permission_item['description'])) {
+    $variables['description'] = $permission_item['description'];
+  }
+  if (!empty($permission_item['warning'])) {
+    $variables['warning'] = $permission_item['warning'];
   }
 }
 
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index bdf5862..7330d95 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -92,8 +92,8 @@ function user_theme() {
   return array(
     'user' => array(
       'render element' => 'elements',
-      'template' => 'user',
       'file' => 'user.pages.inc',
+      'template' => 'user',
     ),
     'user_admin_permissions' => array(
       'render element' => 'form',
@@ -106,12 +106,15 @@ function user_theme() {
     'user_permission_description' => array(
       'variables' => array('permission_item' => NULL, 'hide' => NULL),
       'file' => 'user.admin.inc',
+      'template' => 'user-permission-description',
     ),
     'user_signature' => array(
       'variables' => array('signature' => NULL),
+      'template' => 'user-signature',
     ),
     'username' => array(
       'variables' => array('account' => NULL),
+      'template' => 'username',
     ),
   );
 }
@@ -728,13 +731,19 @@ function user_template_preprocess_default_variables_alter(&$variables) {
 }
 
 /**
- * Preprocesses variables for theme_username().
+ * Preprocesses variables for username.
+ *
+ * Default template: username.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - account: @todo.
+ *   - profile_access: @todo.
+ *   - uid: @todo.
  *
  * Modules that make any changes to variables like 'name' or 'extra' must insure
  * that the final string is safe to include directly in the output by using
  * check_plain() or filter_xss().
- *
- * @see template_process_username()
  */
 function template_preprocess_username(&$variables) {
   $account = $variables['account'];
@@ -779,7 +788,7 @@ function template_preprocess_username(&$variables) {
   // We do not want the l() function to check_plain() a second time.
   $variables['link_options']['html'] = TRUE;
   // Set a default class.
-  $variables['attributes'] = array('class' => array('username'));
+  $variables['attributes'] = new Attribute(array('class' => array('username')));
 }
 
 /**
@@ -802,42 +811,15 @@ function template_process_username(&$variables) {
     // This purposefully does not use
     // \Drupal\Component\Utility\NestedArray::mergeDeep() for performance
     // reasons, since it is potentially called very often.
-    $variables['link_options']['attributes'] = array_merge_recursive($variables['link_attributes'], $variables['attributes']);
-  }
-}
-
-/**
- * Returns HTML for a username, potentially linked to the user's page.
- *
- * @param $variables
- *   An associative array containing:
- *   - account: The user object to format.
- *   - name: The user's name, sanitized.
- *   - extra: Additional text to append to the user's name, sanitized.
- *   - link_path: The path or URL of the user's profile page, home page, or
- *     other desired page to link to for more information about the user.
- *   - link_options: An array of options to pass to the l() function's $options
- *     parameter if linking the user's name to the user's page.
- *   - attributes: An array of attributes to instantiate the
- *     Drupal\Core\Template\Attribute class if not linking to the user's page.
- *
- * @see template_preprocess_username()
- * @see template_process_username()
- */
-function theme_username($variables) {
-  if (isset($variables['link_path'])) {
-    // We have a link path, so we should generate a link using l().
-    // Additional classes may be added as array elements like
-    // $variables['link_options']['attributes']['class'][] = 'myclass';
-    $output = l($variables['name'] . $variables['extra'], $variables['link_path'], $variables['link_options']);
-  }
-  else {
-    // Modules may have added important attributes so they must be included
-    // in the output. Additional classes may be added as array elements like
-    // $variables['attributes']['class'][] = 'myclass';
-    $output = '<span' . new Attribute($variables['attributes']) . '>' . $variables['name'] . $variables['extra'] . '</span>';
+    $attributes = (array) $variables['attributes'];
+    $variables['link_options']['attributes'] = new Attribute(array_merge_recursive($variables['link_attributes'], $attributes));
+    $variables['link'] = array(
+      '#theme' => 'link',
+      '#text' => $variables['name'] . $variables['extra'],
+      '#path' => $variables['link_path'],
+      '#options' => $variables['link_options'],
+    );
   }
-  return $output;
 }
 
 /**
@@ -1688,7 +1670,7 @@ function user_view_page($account) {
  * - $page['content']['#user']:
  *   The user account of the profile being viewed.
  *
- * To theme user profiles, copy modules/user/user.tpl.php
+ * To theme user profiles, copy modules/user/templates/user.html.twig
  * to your theme directory, and edit it as instructed in that file's comments.
  *
  * @param $account
@@ -2294,26 +2276,16 @@ function user_build_filter_query(SelectInterface $query) {
 }
 
 /**
- * Returns HTML for a user signature.
+ * Preprocess variables for user signature templates.
+ *
+ * Default template: user-signature.html.twig.
  *
- * @param $variables
+ * @param array $variables
  *   An associative array containing:
  *   - signature: The user's signature.
- *
- * @ingroup themeable
  */
-function theme_user_signature($variables) {
-  $signature = $variables['signature'];
-  $output = '';
-
-  if ($signature) {
-    $output .= '<div class="clear">';
-    $output .= '<div>—</div>';
-    $output .= $signature;
-    $output .= '</div>';
-  }
-
-  return $output;
+function template_preprocess_user_signature(&$variables) {
+  $variables['attributes'] = new Attribute(array('class' => array('clear')));
 }
 
 /**
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 140b767..2cf3f8c 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -5,6 +5,7 @@
  * User page callback file for the user module.
  */
 
+use Drupal\Core\Template\Attribute;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -175,12 +176,13 @@ function user_logout() {
 }
 
 /**
- * Process variables for user.tpl.php.
+ * Prepares variables for user template.
  *
- * The $variables array contains the following arguments:
- * - $account
+ * Default template: user.html.twig.
  *
- * @see user.tpl.php
+ * @param array $variables
+ *   An associative array containing:
+ *   - $account
  */
 function template_preprocess_user(&$variables) {
   $account = $variables['elements']['#user'];
@@ -192,6 +194,10 @@ function template_preprocess_user(&$variables) {
 
   // Preprocess fields.
   field_attach_preprocess($account, $variables['elements'], $variables);
+
+  // Set up attributes.
+  $variables['attributes'] = array('class' => array('profile'));
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
