diff --git a/theme/viewfield.theme.inc b/theme/viewfield.theme.inc
deleted file mode 100644
index b3eda44..0000000
--- a/theme/viewfield.theme.inc
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-/**
- * @file
- * Theme functions.
- */
-
-/**
- * Return a themed view avoiding viewfield recursion.
- */
-function theme_viewfield_formatter_default($variables) {
-  // $_viewfield_stack keeps a record of the current entity to prevent infinite
-  // recursion during the view rendering process.
-  global $_viewfield_stack;
-
-  $item = $variables['item'];
-  $entity_type = $variables['entity_type'];
-  $entity = $variables['entity'];
-  list($entity_id) = entity_extract_ids($entity_type, $entity);
-
-  if (!empty($item['vname']) && !isset($_viewfield_stack[$entity_type][$entity_id])) {
-    // Push id of current entity unless it's a new entity being previewed.
-    if ($entity_id) {
-      $_viewfield_stack[$entity_type][$entity_id] = $entity_id;
-    }
-    list($view_name, $display) = explode('|', $item['vname'], 2);
-    $view_args = _viewfield_get_view_args($item['vargs'], $entity_type, $entity);
-
-    // Render the view like Views would do.
-    // @see views_embed_view()
-    $view = views_get_view($view_name);
-    if ($view && $view->access($display)) {
-      // Override the view's path to the current path. Otherwise, exposed views
-      // filters would submit to the front page.
-      $view->override_path = $_GET['q'];
-
-      $output = $view->preview($display, $view_args);
-    }
-
-    if ($entity_id) {
-      unset($_viewfield_stack[$entity_type][$entity_id]);
-    }
-
-    // Only return an actual view result to not break empty value behavior.
-    if (isset($output)) {
-      return $output;
-    }
-  }
-}
-
-/**
- * Perform argument replacement
- */
-function _viewfield_get_view_args($vargs, $entity_type, $entity) {
-  $args = array();
-  // Prevent token_replace() from running this function a second time before it
-  // completes the first time.
-  static $replacing_tokens = FALSE;
-  if (!$replacing_tokens && !empty($vargs)) {
-    $pos = 0;
-    while ($pos < strlen($vargs)) {
-      $found = FALSE;
-      // If string starts with a quote, start after quote and get everything
-      // before next quote.
-      if (strpos($vargs, '"', $pos) === $pos) {
-        if (($quote = strpos($vargs, '"', ++$pos)) !== FALSE) {
-          // Skip pairs of quotes.
-          while (!(($ql = strspn($vargs, '"', $quote)) & 1)) {
-            $quote = strpos($vargs, '"', $quote + $ql);
-          }
-          $args[] = str_replace('""', '"', substr($vargs, $pos, $quote + $ql - $pos - 1));
-          $pos = $quote + $ql + 1;
-          $found = TRUE;
-        }
-      }
-      elseif (($comma = strpos($vargs, ',', $pos)) !== FALSE) {
-        // Otherwise, get everything before next comma.
-        $args[] = substr($vargs, $pos, $comma - $pos);
-        // Skip to after comma and repeat
-        $pos = $comma + 1;
-        $found = TRUE;
-      }
-      if (!$found) {
-        $args[] = substr($vargs, $pos);
-        $pos = strlen($vargs);
-      }
-    }
-
-    $replacing_tokens = TRUE;
-    $token_data = array($entity_type => $entity);
-    $entity_loaded = FALSE;
-    foreach ($args as $key => $value) {
-      // Load the full entity only when required.
-      if (!$entity_loaded && _viewfield_token_requires_load($value, $entity_type)) {
-        list($entity_id) = entity_extract_ids($entity_type, $entity);
-        $entities = entity_load($entity_type, array($entity_id));
-        $token_data = array($entity_type => $entities[$entity_id]);
-        $entity_loaded = TRUE;
-      }
-      $args[$key] = token_replace($value, $token_data);
-    }
-    $replacing_tokens = FALSE;
-  }
-  return $args;
-}
-
-/**
- *  Detect if a token requires a full entity to be loaded.
- *
- *  We conservatively require a load if a token refers to the entity type but
- *  the token key is not the entity id. For example, if the $entity_type == '
- *  node' and $entity_id_key == 'nid', we are searching for tokens of the
- *  form [node:some-key] where some-key != 'nid'.
- *  Global tokens (e.g., [current-user:uid]) never require entity loading.
- */
-function _viewfield_token_requires_load($text, $entity_type) {
-  $entity_info = entity_get_info($entity_type);
-  $entity_id_key = $entity_info['entity keys']['id'];
-  $pattern = '/^\[' . $entity_type . ':(.*)\]$/';
-  if (preg_match($pattern, $text, $matches)) {
-    return ($entity_id_key !== $matches[1]);
-  }
-  return FALSE;
-}
-
diff --git a/theme/viewfield.css b/viewfield.css
similarity index 100%
rename from theme/viewfield.css
rename to viewfield.css
diff --git a/viewfield.module b/viewfield.module
index 1af4f1e..bcd0ce1 100644
--- a/viewfield.module
+++ b/viewfield.module
@@ -6,6 +6,17 @@
  */
 
 /**
+ * Implements hook_theme().
+ */
+function viewfield_theme() {
+  return array(
+    'viewfield_formatter_default' => array(
+      'render element' => 'element',
+    ),
+  );
+}
+
+/**
  * Implements hook_field_info().
  */
 function viewfield_field_info() {
@@ -120,16 +131,24 @@ function viewfield_field_formatter_info() {
  * Implements hook_field_formatter_view().
  */
 function viewfield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+  list($entity_id) = entity_extract_ids($entity_type, $entity);
   $elements = array();
   switch ($display['type']) {
     case 'viewfield_default':
       foreach ($items as $delta => $item) {
+        // @todo Store name and display separately.
+        list($view_name, $display) = explode('|', $item['vname'], 2);
         $elements[$delta] = array(
-          '#markup' => theme('viewfield_formatter_default', array(
-            'item' => $item,
-            'entity_type' => $entity_type,
-            'entity' => $entity,
-          )),
+          '#pre_render' => array('viewfield_pre_render'),
+          '#theme' => 'viewfield_formatter_default',
+          '#post_render' => array('viewfield_post_render'),
+          '#view_name' => $view_name,
+          '#view_display' => $display,
+          // @todo Store views arguments serialized.
+          '#view_arguments' => $item['vargs'],
+          '#entity_type' => $entity_type,
+          '#entity_id' => $entity_id,
+          '#entity' => $entity,
         );
       }
       break;
@@ -138,6 +157,68 @@ function viewfield_field_formatter_view($entity_type, $entity, $field, $instance
 }
 
 /**
+ * #pre_render callback for a viewfield field.
+ *
+ * @see viewfield_field_formatter_view()
+ * @see viewfield_post_render()
+ */
+function viewfield_pre_render($element) {
+  $stack = &drupal_static('viewfield_stack', array());
+
+  // During previews of new entities, there is no entity ID yet, and thus, also
+  // no recursion to prevent.
+  if (empty($element['#entity_id'])) {
+    return $element;
+  }
+
+  // Abort rendering in case of recursion.
+  if (isset($stack[$element['#entity_type']][$element['#entity_id']])) {
+    $element['#printed'] = TRUE;
+  }
+  // Otherwise, add the rendered entity to the stack to prevent recursion.
+  else {
+    $stack[$element['#entity_type']][$element['#entity_id']] = TRUE;
+  }
+  return $element;
+}
+
+/**
+ * #post_render callback for a viewfield field.
+ *
+ * @see viewfield_pre_render()
+ * @see viewfield_field_formatter_view()
+ */
+function viewfield_post_render($element) {
+  $stack = &drupal_static('viewfield_stack', array());
+
+  unset($stack[$element['#entity_type']][$element['#entity_id']]);
+
+  return $element;
+}
+
+/**
+ * Return HTML for a view in a field.
+ *
+ * @see views_embed_view()
+ */
+function theme_viewfield_formatter_default($variables) {
+  $element = $variables['element'];
+
+  // @todo Move load + access check into #pre_render or viewfield_field_formatter_view()?
+  $view = views_get_view($element['#view_name']);
+  if ($view && $view->access($element['#view_display'])) {
+    // Override the view's path to the current path. Otherwise, exposed views
+    // filters would submit to the front page.
+    $view->override_path = $_GET['q'];
+
+    $view_args = _viewfield_get_view_args($element['#view_arguments'], $element['#entity_type'], $element['#entity']);
+
+    // Only return an actual view result to not break empty value behavior.
+    return $view->preview($element['#view_display'], $view_args);
+  }
+}
+
+/**
  * Implements hook_field_widget_info().
  */
 function viewfield_field_widget_info() {
@@ -224,7 +305,7 @@ function viewfield_field_widget_form(&$form, &$form_state, $field, $instance, $l
   // @todo Core: Fix bogus white-space: nowrap.
   // @see http://drupal.org/node/1230336
   $element['#attached']['css'] = array(
-    drupal_get_path('module', 'viewfield') . '/theme/viewfield.css' => array(
+    drupal_get_path('module', 'viewfield') . '/viewfield.css' => array(
       'weight' => 1,
     ),
   );
@@ -239,7 +320,7 @@ function _viewfield_potential_references($field, $instance, $delta = 0) {
   // Retrieve all currently available views.
   $views = views_get_enabled_views();
   // Limit to allowed values, if any.
-  if (!empty($field['settings']['allowed_views']) && is_array($field['settings']['allowed_views'])) {
+  if (0 && !empty($field['settings']['allowed_views']) && is_array($field['settings']['allowed_views'])) {
     $views = array_intersect_key($views, array_filter($field['settings']['allowed_views']));
   }
   $options = array();
@@ -252,19 +333,78 @@ function _viewfield_potential_references($field, $instance, $delta = 0) {
 }
 
 /**
- * Implements hook_theme().
+ * Perform argument replacement
  */
-function viewfield_theme() {
-  return array(
-    'viewfield_formatter_default' => array(
-      'variables' => array(
-        'item' => NULL,
-        'entity_type' => NULL,
-        'entity' => NULL,
-      ),
-      'file' => 'theme/viewfield.theme.inc',
-    ),
-  );
+function _viewfield_get_view_args($vargs, $entity_type, $entity) {
+  $args = array();
+  // Prevent token_replace() from running this function a second time before it
+  // completes the first time.
+  static $replacing_tokens = FALSE;
+  if (!$replacing_tokens && !empty($vargs)) {
+    $pos = 0;
+    while ($pos < strlen($vargs)) {
+      $found = FALSE;
+      // If string starts with a quote, start after quote and get everything
+      // before next quote.
+      if (strpos($vargs, '"', $pos) === $pos) {
+        if (($quote = strpos($vargs, '"', ++$pos)) !== FALSE) {
+          // Skip pairs of quotes.
+          while (!(($ql = strspn($vargs, '"', $quote)) & 1)) {
+            $quote = strpos($vargs, '"', $quote + $ql);
+          }
+          $args[] = str_replace('""', '"', substr($vargs, $pos, $quote + $ql - $pos - 1));
+          $pos = $quote + $ql + 1;
+          $found = TRUE;
+        }
+      }
+      elseif (($comma = strpos($vargs, ',', $pos)) !== FALSE) {
+        // Otherwise, get everything before next comma.
+        $args[] = substr($vargs, $pos, $comma - $pos);
+        // Skip to after comma and repeat
+        $pos = $comma + 1;
+        $found = TRUE;
+      }
+      if (!$found) {
+        $args[] = substr($vargs, $pos);
+        $pos = strlen($vargs);
+      }
+    }
+
+    $replacing_tokens = TRUE;
+    $token_data = array($entity_type => $entity);
+    $entity_loaded = FALSE;
+    foreach ($args as $key => $value) {
+      // Load the full entity only when required.
+      if (!$entity_loaded && _viewfield_token_requires_load($value, $entity_type)) {
+        list($entity_id) = entity_extract_ids($entity_type, $entity);
+        $entities = entity_load($entity_type, array($entity_id));
+        $token_data = array($entity_type => $entities[$entity_id]);
+        $entity_loaded = TRUE;
+      }
+      $args[$key] = token_replace($value, $token_data);
+    }
+    $replacing_tokens = FALSE;
+  }
+  return $args;
+}
+
+/**
+ *  Detect if a token requires a full entity to be loaded.
+ *
+ *  We conservatively require a load if a token refers to the entity type but
+ *  the token key is not the entity id. For example, if the $entity_type == '
+ *  node' and $entity_id_key == 'nid', we are searching for tokens of the
+ *  form [node:some-key] where some-key != 'nid'.
+ *  Global tokens (e.g., [current-user:uid]) never require entity loading.
+ */
+function _viewfield_token_requires_load($text, $entity_type) {
+  $entity_info = entity_get_info($entity_type);
+  $entity_id_key = $entity_info['entity keys']['id'];
+  $pattern = '/^\[' . $entity_type . ':(.*)\]$/';
+  if (preg_match($pattern, $text, $matches)) {
+    return ($entity_id_key !== $matches[1]);
+  }
+  return FALSE;
 }
 
 /**
