diff --git a/includes/admin.settings.inc b/includes/admin.settings.inc
index 7b15f1a..4bc9cb9 100644
--- a/includes/admin.settings.inc
+++ b/includes/admin.settings.inc
@@ -53,16 +53,17 @@ function signup_settings_form() {
   );
 
   $form['adv_settings']['signup_form_location'] = array(
-    '#title' => t('Location of the signup form'),
+    '#title' => t('Default location of the signup control'),
     '#type' => 'radios',
     '#options' => array(
-      'node' => t('Included on each node'),
-      'tab' => t('Under the %signups tab', array('%signups' => t('Signups'))),
-      'none' => t('Do not display signup form'),
+      'node' => t('Include a form on each node'),
+      'link' => t('Include a toggle link'),
+      'tab' => t('Form under the %signups tab', array('%signups' => t('Signups'))),
+      'none' => t('Do not display any signup control'),
     ),
     '#default_value' => variable_get('signup_form_location', 'node'),
-    '#description' => t('On every signup-enabled node, users with permission to
- sign up can be presented with a form. This setting controls where this form should be displayed: either directly on the node itself, on a separate tab, or not at all.'),
+    '#description' => t('On every signup-enabled node (unless overridden in the content type settings), users with permission to
+ sign up can be presented with a form, a link, or a tag. This setting controls what and where it should be displayed: either directly on the node itself as a form or link, on a separate tab as a form, or not at all.'),
     '#prefix' => '<div class="signup-form-location-radios">',
     '#suffix' => '</div>',
   );
diff --git a/includes/node_output.inc b/includes/node_output.inc
index 759f6ce..e99cc7c 100644
--- a/includes/node_output.inc
+++ b/includes/node_output.inc
@@ -95,12 +95,34 @@ function _signup_current_user_signup($node, $type = 'node') {
         if (user_access('sign up for content')) {
           // User has permission to do so, so give them the form.
           module_load_include('inc', 'signup', 'includes/signup_form');
-          $output .= drupal_get_form('signup_form', $node, 'auth', $fieldset);
+          if ($type == 'node') {
+            $output .= drupal_get_form('signup_form', $node, 'auth', $fieldset);
+          }
+          elseif ($type == 'link') {
+            $path = drupal_get_path('module', 'signup');
+            drupal_add_js("$path/js/signup_link.js");
+            // Generate token.
+            $url = 'node/' . $node->nid . '/signup';
+            $token = drupal_get_token($url);
+            $signup_url = url($url, array('query' => array('token' => $token)));
+            $output .= theme('signup_link', $node, $signup_url, t('Signup'));
+          }
         }
       }
       else {
         // Already signed up, display their info.
-        $output .= _signup_render_signup_edit_form($signup, $type);
+        if ($type == 'node') {
+          $output .= _signup_render_signup_edit_form($signup, $type);
+        }
+        elseif ($type == 'link') {
+          $path = drupal_get_path('module', 'signup');
+          drupal_add_js("$path/js/signup_link.js");
+          // Generate token.
+          $url = 'node/' . $node->nid . '/signup';
+          $token = drupal_get_token($url);
+          $signup_url = url($url, array('query' => array('token' => $token)));
+          $output .= theme('signup_link', $node, $signup_url, t('Cancel signup'));
+        }
       }
     }
   }
diff --git a/includes/signup_form.inc b/includes/signup_form.inc
index ce49d8d..54d4d08 100644
--- a/includes/signup_form.inc
+++ b/includes/signup_form.inc
@@ -196,3 +196,41 @@ function signup_node_admin_add_user_page($node) {
   return $output;
 }
 
+/**
+ * Page callback for signup toggle link.
+ */
+function signup_link_signup($node) {
+  global $user;
+  // @todo do I need to check user_access('sign up for content') or does _signup_menu_access check that/can?
+  // Check for token.
+  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'node/' . $node->nid . '/signup')) {
+    return drupal_access_denied();
+  }
+  $js = FALSE;
+  if (isset($_GET['js']) && intval($_GET['js']) == 1) {
+    $js = TRUE;
+  }
+  // Sign-up or cancel.
+  $signup = db_fetch_object(db_query("SELECT sl.*, n.title, u.name, u.mail FROM {signup_log} sl INNER JOIN {node} n ON sl.nid = n.nid INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.uid = %d AND sl.nid = %d", $user->uid, $node->nid));
+  if (empty($signup)) {
+    // Mock signup_form.
+    $signup_form = array(
+      'nid' => $node->nid,
+      'uid' => $user->uid,
+    );
+    $result = signup_sign_up_user($signup_form, !$js);
+    if ($result !== FALSE) {
+      $message = t('Cancel signup');
+    }
+  }
+  else {
+    signup_cancel_signup($signup->sid, !$js);
+    $message = t('Signup');
+  }
+  if ($js) {
+    drupal_json($message);
+    return;
+  }
+  // We weren't invoked via JS so set a message and return to the node.
+  drupal_goto('node/' . $node->nid);
+}
\ No newline at end of file
diff --git a/js/signup_link.js b/js/signup_link.js
new file mode 100644
index 0000000..39036b2
--- /dev/null
+++ b/js/signup_link.js
@@ -0,0 +1,9 @@
+Drupal.behaviors.signupLink = function(context) {
+  $('.signup-link').click(function() {
+    anchor = $(this);
+    $.getJSON(anchor.attr('href') + '&js=1', function(data) {
+      anchor.text(data);
+    });
+    return false;
+  });
+}
\ No newline at end of file
diff --git a/signup.module b/signup.module
index 00dfce9..6686388 100644
--- a/signup.module
+++ b/signup.module
@@ -156,6 +156,15 @@ function signup_theme() {
         'view' => NULL,
       ),
     ),
+    'signup_link' => array(
+      'file' => 'node.inc',
+      'path' => drupal_get_path('module', 'signup') .'/includes',
+      'arguments' => array(
+        'node' => NULL,
+        'signup_url' => NULL,
+        'signup_text' => NULL,
+      ),
+    ),
   );
 }
 
@@ -406,6 +415,16 @@ function signup_menu() {
     'file' => 'broadcast.inc',
     'file path' => $path,
   );
+  $items['node/%node/signup'] = array(
+    'title' => 'Signup link',
+    'page callback' => 'signup_link_signup',
+    'page arguments' => array(1),
+    'access callback' => '_signup_menu_access',
+    'access arguments' => array(1, 'link'),
+    'type' => MENU_CALLBACK,
+    'file' => 'signup_form.inc',
+    'file path' => $path,
+  );
 
   return $items;
 }
@@ -499,6 +518,10 @@ function _signup_menu_access($node, $menu_type = 'node') {
       $admin = _signup_menu_access($node, 'admin');
       $email = _signup_menu_access($node, 'broadcast');
       return $signup || $list || $admin || $email;
+
+    case 'link':
+      $location = variable_get('signup_form_location_' . $node->type, variable_get('signup_form_location', 'node'));
+      return $location == 'link' ? TRUE : FALSE;
   }
 }
 
@@ -734,6 +757,21 @@ function signup_form_node_type_form_alter(&$form, &$form_state) {
     '#default_value' => variable_get('signup_node_default_state_'. $type, 'disabled'),
     '#description' => t('If %disabled is selected, signups will not be possible for this content type. If %allowed_off is selected, signups will be off by default, but users with the %admin_all_signups permission will be able to allow signups for specific posts of this content type. If %enabled_on is selected, users will be allowed to signup for this content type unless an administrator disbles signups on specific posts.', array('%disabled' => t('Disabled'), '%allowed_off' => t('Allowed (off by default)'), '%enabled_on' => t('Enabled (on by default)'), '%admin_all_signups' => t('administer all signups'))),
   );
+  $form['signup']['signup_form_location'] = array(
+    '#title' => t('Location of the signup control'),
+    '#type' => 'radios',
+    '#options' => array(
+      'node' => t('Include a form on each node'),
+      'link' => t('Include a toggle link'),
+      'tab' => t('Form under the %signups tab', array('%signups' => t('Signups'))),
+      'none' => t('Do not display any signup control'),
+    ),
+    '#default_value' => variable_get('signup_form_location_' . $type, variable_get('signup_form_location', 'node')),
+    '#description' => t('On every signup-enabled node, users with permission to
+ sign up can be presented with a form, a link, or a tag. This setting controls what and where it should be displayed: either directly on the node itself as a form or link, on a separate tab as a form, or not at all.'),
+    '#prefix' => '<div class="signup-form-location-radios">',
+    '#suffix' => '</div>',
+  );
 
   if (!empty($type) && function_exists('_signup_date_alter_node_type_form')) {
     _signup_date_alter_node_type_form($form, $form_state);
@@ -796,13 +834,13 @@ function signup_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
       // Only include any of this if we're trying to view the node as
       // a page, not during the view from comment validation, etc.
       if ($page && _signup_needs_output($node)) {
-        $info_location = variable_get('signup_form_location', 'node');
+        $info_location = variable_get('signup_form_location_' . $node->type, variable_get('signup_form_location', 'node'));
         $list_location = variable_get('signup_display_signup_user_list', 'embed-view');
         if ($info_location == 'node' || $list_location == 'embed-view') {
           module_load_include('inc', 'signup', 'includes/node_output');
         }
-        if ($info_location == 'node') {
-          $signup_info = _signup_node_output($node);
+        if ($info_location == 'node' || $info_location == 'link') {
+          $signup_info = _signup_node_output($node, $info_location);
           if (!empty($signup_info)) {
             if (module_exists('content')) {
               // Due to a bug in CCK (http://drupal.org/node/363456), we need
diff --git a/theme/node.inc b/theme/node.inc
index 1c3c4b9..3a4645a 100644
--- a/theme/node.inc
+++ b/theme/node.inc
@@ -58,6 +58,25 @@ function theme_signup_node_output_header($node) {
   return '<a name="signup"></a>';
 }
 
+/**
+ * Return HTML for the dynamic signup link.
+ *
+ * @param $node
+ *   The fully loaded node object.
+ * @param $signup_url
+ *   Signup link URL with token.
+ * @param $signup_text
+ *   Text to appear in link.
+ *
+ * @return
+ *   HTML to display.
+ *
+ */
+function theme_signup_link($node, $signup_url, $signup_text) {
+  // Class signup-link required for AJAX toggle.
+  return '<a href="' . $signup_url . '" class="signup-link">' . $signup_text . '</a>';
+}
+
 /*
  * Return HTML desired when displaying a node title along with the signup date.
  */
diff --git a/views/handlers/signup_handler_field_signup_link.inc b/views/handlers/signup_handler_field_signup_link.inc
new file mode 100644
index 0000000..8c45da3
--- /dev/null
+++ b/views/handlers/signup_handler_field_signup_link.inc
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Field handler to generate a toggle signup link.
+ */
+class signup_handler_field_signup_link extends views_handler_field_node_link {
+  function construct() {
+    require_once drupal_get_path('module', 'signup') . '/theme/node.inc';
+    parent::construct();
+    $this->additional_fields['uid'] = array('table' => 'signup_log', 'field' => 'uid');
+    $this->additional_fields['signup_status'] = array('table' => 'signup', 'field' => 'status');
+  }
+
+  function render($values) {
+    global $user;
+
+    // Check access to signup.
+    $node = new StdClass();
+    $node->nid = $values->nid;
+    $node->uid = $values->node_uid;
+    $node->signup_status = $values->{$this->aliases['signup_status']};
+    $node->signup = isset($values->{$this->aliases['signup_status']});
+    if (!_signup_menu_access($node, 'link')) {
+      return;
+    }
+
+    $url = "node/$node->nid/signup";
+    $path = drupal_get_path('module', 'signup');
+    drupal_add_js("$path/js/signup_link.js");
+    // Generate token.
+    $token = drupal_get_token($url);
+    $signup_url = url($url, array('query' => array('token' => $token)));
+    $signup = db_fetch_object(db_query("SELECT sl.*, n.title, u.name, u.mail FROM {signup_log} sl INNER JOIN {node} n ON sl.nid = n.nid INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.uid = %d AND sl.nid = %d", $user->uid, $node->nid));
+    if (empty($signup)) {
+      $output = theme('signup_link', $node, $signup_url, t('Signup'));
+    }
+    else {
+      $output = theme('signup_link', $node, $signup_url, t('Cancel signup'));
+    }
+    return $output;
+  }
+}
+
diff --git a/views/signup.views.inc b/views/signup.views.inc
index 1366b5c..4632669 100644
--- a/views/signup.views.inc
+++ b/views/signup.views.inc
@@ -301,6 +301,14 @@ function signup_views_data() {
     ),
   );
 
+  $data['signup_log']['signup_link'] = array(
+    'title' => t('User: Signup link'),
+    'field' => array(
+      'handler' => 'signup_handler_field_signup_link',
+      'help' => t('Toggle link to signup or cancel signup.'),
+    ),
+  );
+
   return $data;
 }
 
@@ -322,6 +330,9 @@ function signup_views_handlers() {
       'signup_handler_field_signup_edit_link' => array(
         'parent' => 'views_handler_field_node_link',
       ),
+      'signup_handler_field_signup_link' => array(
+        'parent' => 'views_handler_field_node_link',
+      ),
       'signup_handler_field_signup_node_link' => array(
         'parent' => 'views_handler_field_node_link',
       ),
