diff --git a/bp-members/admin/css/admin.css b/bp-members/admin/css/admin.css
index 5473fe5..5d295da 100644
--- a/bp-members/admin/css/admin.css
+++ b/bp-members/admin/css/admin.css
@@ -15,14 +15,6 @@ div#profile-page.wrap form#your-profile ul#profile-nav {
 	width:100%;
 }
 
-div#community-profile-page h2:first-of-type {
-	margin-bottom:1em;
-}
-
-div#community-profile-page  h2.profile-section {
-	border-bottom:dotted 1px #ccc;
-}
-
 div#community-profile-page ul#profile-nav {
 	border-bottom:solid 1px #ccc;
 	width:100%;
@@ -104,40 +96,69 @@ div#community-profile-page p.not-activated {
 	color:red;
 }
 
-div#community-profile-page .form-table td.admin-field-visibility-td {
-	padding:5px 5px 15px 5px;
+.alt {
+	background: none;
 }
-
-div#community-profile-page .form-table tr.admin-field-visibility-tr {
-	border-bottom:dotted 1px #ccc;
+.bp-profile-field {
+	border-bottom: dotted 1px #ccc;
+	font-size: 14px;
+	margin: 15px 0;
 }
-
-div#community-profile-page .form-table tr.admin-field-visibility-tr:last-child{
-	border:none;
+.bp-profile-field:last-child {
+	border-bottom: 0;
 }
-
-div#community-profile-page .field-visibility-settings legend, 
-div#community-profile-page .field-visibility-settings-notoggle {
+.bp-profile-field p {
 	font-size: 14px;
-	font-style: italic;
-	color:#666;
 }
-
-div#community-profile-page .field-visibility-settings ul {
-	margin:0;
-	font-size: 13px;
+.bp-profile-field .datebox > label,
+.bp-profile-field .radio > label,
+.bp-profile-field > label {  /* label takes on left side 200px */
+	display: inline-block;
+	font-weight: 600;
+	text-align: left;
+	vertical-align: middle;
+	width: 200px;
 }
 
-div#community-profile-page .field-visibility-settings ul li {
-	display:inline-block;
-	margin-right:1em;
+.field_type_textarea > label,
+.field_type_multiselectbox > label,
+.field_type_radio .radio > label,
+.field_type_checkbox .checkbox > label {  /* these fields are usually pretty tall, so align the label for better consistency */
+	vertical-align: top;
 }
-
-div#community-profile-page .form-table td.admin-field-radio label, 
-div#community-profile-page .form-table td.admin-field-checkbox label {
-	display:block;
+.bp-profile-field .description {  /* description also sits in the right side column */
+	margin: 10px 0 10px 200px;
+	text-align: left;
 }
-
-.field-visibility-settings {
+.clear-value {  /* 'clear value' option also sits in the right side column */
+	display: block;
+	font-size: 12px;
+	margin-left: 200px;
+}
+.field_type_checkbox .checkbox > label + label {  /* force checkboxes to new lines, in the right side column */
+	display: block;
+	margin-left: 200px;
+	width: auto;
+}
+.field_type_radio .radio div:not(.field-visibility-settings) label {  /* force radio buttons to new lines */
+	display: block;
+}
+.field_type_radio .radio div:not(.field-visibility-settings) {  /* make the radio buttons sit in the right side column */
+	display: inline-block;
+}
+.field-visibility-settings-notoggle,
+.field-visibility-settings-toggle {  /* visibility settings go in the left column */
+	margin: 10px 0 10px 200px;
+	text-align: left;
+}
+.field-visibility-settings {  /* visibility settings go in the left column */
 	display: none;
-}
\ No newline at end of file
+	margin-left: 200px;
+}
+.field-visibility-settings .button {  /* visibility setting close button */
+	margin-bottom: 15px;
+}
+#normal-sortables .field-visibility-settings legend {  /* id required for css selector weight */
+	font-size: 16px;
+	margin-bottom: 10px;
+}
diff --git a/bp-templates/bp-legacy/buddypress/members/register.php b/bp-templates/bp-legacy/buddypress/members/register.php
index 8679dc2..6624817 100644
--- a/bp-templates/bp-legacy/buddypress/members/register.php
+++ b/bp-templates/bp-legacy/buddypress/members/register.php
@@ -68,92 +68,13 @@
 
 						<div class="editfield">
 
-							<?php if ( 'textbox' == bp_get_the_profile_field_type() ) : ?>
+							<?php
+							$field_type = bp_xprofile_create_field_type( bp_get_the_profile_field_type() );
+							$field_type->edit_field_html();
 
-								<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-								<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-								<input type="text" name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" value="<?php bp_the_profile_field_edit_value(); ?>" />
+							do_action( 'bp_custom_profile_edit_fields_pre_visibility' );
 
-							<?php endif; ?>
-
-							<?php if ( 'textarea' == bp_get_the_profile_field_type() ) : ?>
-
-								<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-								<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-								<textarea rows="5" cols="40" name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_edit_value(); ?></textarea>
-
-							<?php endif; ?>
-
-							<?php if ( 'selectbox' == bp_get_the_profile_field_type() ) : ?>
-
-								<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-								<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-								<select name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>">
-									<?php bp_the_profile_field_options(); ?>
-								</select>
-
-							<?php endif; ?>
-
-							<?php if ( 'multiselectbox' == bp_get_the_profile_field_type() ) : ?>
-
-								<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-								<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-								<select name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" multiple="multiple">
-									<?php bp_the_profile_field_options(); ?>
-								</select>
-
-							<?php endif; ?>
-
-							<?php if ( 'radio' == bp_get_the_profile_field_type() ) : ?>
-
-								<div class="radio">
-									<span class="label"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></span>
-
-									<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-									<?php bp_the_profile_field_options(); ?>
-
-									<?php if ( !bp_get_the_profile_field_is_required() ) : ?>
-										<a class="clear-value" href="javascript:clear( '<?php bp_the_profile_field_input_name(); ?>' );"><?php _e( 'Clear', 'buddypress' ); ?></a>
-									<?php endif; ?>
-								</div>
-
-							<?php endif; ?>
-
-							<?php if ( 'checkbox' == bp_get_the_profile_field_type() ) : ?>
-
-								<div class="checkbox">
-									<span class="label"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></span>
-
-									<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-									<?php bp_the_profile_field_options(); ?>
-								</div>
-
-							<?php endif; ?>
-
-							<?php if ( 'datebox' == bp_get_the_profile_field_type() ) : ?>
-
-								<div class="datebox">
-									<label for="<?php bp_the_profile_field_input_name(); ?>_day"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-									<?php do_action( bp_get_the_profile_field_errors_action() ); ?>
-
-									<select name="<?php bp_the_profile_field_input_name(); ?>_day" id="<?php bp_the_profile_field_input_name(); ?>_day">
-										<?php bp_the_profile_field_options( 'type=day' ); ?>
-									</select>
-
-									<select name="<?php bp_the_profile_field_input_name(); ?>_month" id="<?php bp_the_profile_field_input_name(); ?>_month">
-										<?php bp_the_profile_field_options( 'type=month' ); ?>
-									</select>
-
-									<select name="<?php bp_the_profile_field_input_name(); ?>_year" id="<?php bp_the_profile_field_input_name(); ?>_year">
-										<?php bp_the_profile_field_options( 'type=year' ); ?>
-									</select>
-								</div>
-
-							<?php endif; ?>
-
-							<?php do_action( 'bp_custom_profile_edit_fields_pre_visibility' ); ?>
-
-							<?php if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
+							if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
 								<p class="field-visibility-settings-toggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
 									<?php printf( __( 'This field can be seen by: <span class="current-visibility-level">%s</span>', 'buddypress' ), bp_get_the_profile_field_visibility_level_label() ) ?> <a href="#" class="visibility-toggle-link"><?php _ex( 'Change', 'Change profile field visibility level', 'buddypress' ); ?></a>
 								</p>
diff --git a/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php b/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php
index b7b6cc5..a20c079 100644
--- a/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php
+++ b/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php
@@ -21,99 +21,12 @@ if ( bp_has_profile( 'profile_group_id=' . bp_get_current_profile_group_id() ) )
 
 			<div<?php bp_field_css_class( 'editfield' ); ?>>
 
-				<?php if ( 'textbox' == bp_get_the_profile_field_type() ) : ?>
+				<?php
+				$field_type = bp_xprofile_create_field_type( bp_get_the_profile_field_type() );
+				$field_type->edit_field_html();
 
-					<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-					<input type="text" name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" value="<?php bp_the_profile_field_edit_value(); ?>" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>/>
-
-				<?php endif; ?>
-
-				<?php if ( 'textarea' == bp_get_the_profile_field_type() ) : ?>
-
-					<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-					<textarea rows="5" cols="40" name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>><?php bp_the_profile_field_edit_value(); ?></textarea>
-
-				<?php endif; ?>
-
-				<?php if ( 'selectbox' == bp_get_the_profile_field_type() ) : ?>
-
-					<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-					<select name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-						<?php bp_the_profile_field_options(); ?>
-					</select>
-
-				<?php endif; ?>
-
-				<?php if ( 'multiselectbox' == bp_get_the_profile_field_type() ) : ?>
-
-					<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-					<select name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" multiple="multiple" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-						<?php bp_the_profile_field_options(); ?>
-
-					</select>
-
-					<?php if ( !bp_get_the_profile_field_is_required() ) : ?>
-
-						<a class="clear-value" href="javascript:clear( '<?php bp_the_profile_field_input_name(); ?>' );"><?php _e( 'Clear', 'buddypress' ); ?></a>
-
-					<?php endif; ?>
-
-				<?php endif; ?>
-
-				<?php if ( 'radio' == bp_get_the_profile_field_type() ) : ?>
-
-					<div class="radio">
-						<span class="label"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></span>
-
-						<?php bp_the_profile_field_options(); ?>
-
-						<?php if ( !bp_get_the_profile_field_is_required() ) : ?>
-
-							<a class="clear-value" href="javascript:clear( '<?php bp_the_profile_field_input_name(); ?>' );"><?php _e( 'Clear', 'buddypress' ); ?></a>
-
-						<?php endif; ?>
-					</div>
-
-				<?php endif; ?>
-
-				<?php if ( 'checkbox' == bp_get_the_profile_field_type() ) : ?>
-
-					<div class="checkbox">
-						<span class="label"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></span>
-
-						<?php bp_the_profile_field_options(); ?>
-					</div>
-
-				<?php endif; ?>
-
-				<?php if ( 'datebox' == bp_get_the_profile_field_type() ) : ?>
-
-					<div class="datebox">
-						<label for="<?php bp_the_profile_field_input_name(); ?>_day"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-
-						<select name="<?php bp_the_profile_field_input_name(); ?>_day" id="<?php bp_the_profile_field_input_name(); ?>_day" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-							<?php bp_the_profile_field_options( 'type=day' ); ?>
-
-						</select>
-
-						<select name="<?php bp_the_profile_field_input_name(); ?>_month" id="<?php bp_the_profile_field_input_name(); ?>_month" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-							<?php bp_the_profile_field_options( 'type=month' ); ?>
-
-						</select>
-
-						<select name="<?php bp_the_profile_field_input_name(); ?>_year" id="<?php bp_the_profile_field_input_name(); ?>_year" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-							<?php bp_the_profile_field_options( 'type=year' ); ?>
-
-						</select>
-					</div>
-
-				<?php endif; ?>
-
-				<?php do_action( 'bp_custom_profile_edit_fields_pre_visibility' ); ?>
+				do_action( 'bp_custom_profile_edit_fields_pre_visibility' );
+				?>
 
 				<?php if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
 					<p class="field-visibility-settings-toggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
diff --git a/bp-templates/bp-legacy/css/buddypress.css b/bp-templates/bp-legacy/css/buddypress.css
index befd22c..773519a 100644
--- a/bp-templates/bp-legacy/css/buddypress.css
+++ b/bp-templates/bp-legacy/css/buddypress.css
@@ -607,8 +607,8 @@ body.activity-permalink #buddypress div.activity-comments div.acomment-content {
 	font-weight: bold;
 	margin: 15px 0 5px 0;
 }
-#buddypress .standard-form div.checkbox label,
-#buddypress .standard-form div.radio label {
+#buddypress .standard-form div.checkbox label:nth-child(n+2),
+#buddypress .standard-form div.radio div label {
 	color: #888;
 	font-size: 100%;
 	font-weight: normal;
diff --git a/bp-xprofile/bp-xprofile-admin.php b/bp-xprofile/bp-xprofile-admin.php
index 1c275db..eca6366 100644
--- a/bp-xprofile/bp-xprofile-admin.php
+++ b/bp-xprofile/bp-xprofile-admin.php
@@ -382,69 +382,12 @@ function xprofile_admin_field( $admin_field, $admin_group, $class = '' ) {
 		<legend><span><?php bp_the_profile_field_name(); ?> <?php if( !$field->can_delete ) : ?> <?php _e( '(Primary)', 'buddypress' ); endif; ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(Required)', 'buddypress' ) ?><?php endif; ?></span></legend>
 		<div class="field-wrapper">
 
-			<?php switch ( $field->type ) : case 'textbox' : ?>
-
-				<input type="text" name="<?php bp_the_profile_field_input_name() ?>" id="<?php bp_the_profile_field_input_name() ?>" value="" />
-
-			<?php break; case 'textarea' : ?>
-
-				<textarea rows="5" cols="40" name="<?php bp_the_profile_field_input_name() ?>" id="<?php bp_the_profile_field_input_name() ?>"></textarea>
-
-			<?php break; case 'selectbox' : ?>
-
-				<select name="<?php bp_the_profile_field_input_name() ?>" id="<?php bp_the_profile_field_input_name() ?>">
-
-					<?php bp_the_profile_field_options() ?>
-
-				</select>
-
-			<?php break; case 'multiselectbox' : ?>
-
-				<select name="<?php bp_the_profile_field_input_name() ?>" id="<?php bp_the_profile_field_input_name() ?>" multiple="multiple">
-
-					<?php bp_the_profile_field_options() ?>
-
-				</select>
-
-			<?php break; case 'radio' : ?>
-
-				<?php bp_the_profile_field_options() ?>
-
-				<?php if ( !bp_get_the_profile_field_is_required() ) : ?>
-
-					<a class="clear-value" href="javascript:clear( '<?php bp_the_profile_field_input_name() ?>' );"><?php _e( 'Clear', 'buddypress' ) ?></a>
-
-				<?php endif; ?>
-
-			<?php break; case 'checkbox' : ?>
-
-				<?php bp_the_profile_field_options(); ?>
-
-			<?php break; case 'datebox' : ?>
-
-				<select name="<?php bp_the_profile_field_input_name(); ?>_day" id="<?php bp_the_profile_field_input_name(); ?>_day">
-
-					<?php bp_the_profile_field_options( 'type=day' ); ?>
-
-				</select>
-
-				<select name="<?php bp_the_profile_field_input_name(); ?>_month" id="<?php bp_the_profile_field_input_name(); ?>_month">
-
-					<?php bp_the_profile_field_options( 'type=month' ); ?>
-
-				</select>
-
-				<select name="<?php bp_the_profile_field_input_name(); ?>_year" id="<?php bp_the_profile_field_input_name(); ?>_year">
-
-					<?php bp_the_profile_field_options( 'type=year' ); ?>
-
-				</select>
-
-			<?php break; default : ?>
-
-			<?php do_action( 'xprofile_admin_field', $field, 1 ); ?>
+			<?php
+			$field_type = bp_xprofile_create_field_type( $field->type );
+			$field_type->admin_field_html();
 
-			<?php endswitch; ?>
+			do_action( 'xprofile_admin_field', $field, 1 );
+			?>
 
 			<?php if ( $field->description ) : ?>
 
@@ -467,6 +410,57 @@ function xprofile_admin_field( $admin_field, $admin_group, $class = '' ) {
 <?php
 }
 
+/**
+ * Print <option> elements containing the xprofile field types.
+ *
+ * @param string $select_field_type The name of the field type that should be selected. Will defaults to "textbox" if NULL is passed.
+ * @since BuddyPress (2.0.0)
+ */
+function bp_xprofile_admin_form_field_types( $select_field_type ) {
+	$categories = array();
+
+	if ( is_null( $select_field_type ) ) {
+		$select_field_type = 'textbox';
+	}
+
+	// Sort each field type into its category
+	foreach ( bp_xprofile_get_field_types() as $field_name => $field_class ) {
+		$field_type_obj = new $field_class;
+		$the_category   = $field_type_obj->category;
+
+		// Fallback to a catch-all if category not set
+		if ( ! $the_category ) {
+			$the_category = _x( 'Other', 'xprofile field type category', 'buddypress' );
+		}
+
+		if ( isset( $categories[$the_category] ) ) {
+			$categories[$the_category][] = array( $field_name, $field_type_obj );
+		} else {
+			$categories[$the_category] = array( array( $field_name, $field_type_obj ) );
+		}
+	}
+
+	// Sort the categories alphabetically. ksort()'s SORT_NATURAL is only in PHP >= 5.4 :((
+	uksort( $categories, 'strnatcmp' );
+
+	// Loop through each category and output form <options>
+	foreach ( $categories as $category => $fields ) {
+		printf( '<optgroup label="%1$s">', esc_attr( $category ) );  // Already i18n'd in each profile type class
+
+		// Sort these fields types alphabetically
+		uasort( $fields, create_function( '$a, $b', 'return strnatcmp( $a[1]->name, $b[1]->name );' ) );
+
+		foreach ( $fields as $field_type_obj ) {
+			$field_name     = $field_type_obj[0];
+			$field_type_obj = $field_type_obj[1];
+
+			printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $field_name ), selected( $select_field_type, $field_name, false ), esc_html( $field_type_obj->name ) );
+		}
+
+		printf( '</optgroup>' );
+	}
+}
+
 if ( ! class_exists( 'BP_XProfile_User_Admin' ) ) :
 /**
  * Load xProfile Profile admin area.
@@ -642,7 +636,7 @@ class BP_XProfile_User_Admin {
 				// Set the errors var
 				$errors = false;
 
-				// Now we've checked for required fields, lets save the values.
+				// Now we've checked for required fields, let's save the values.
 				foreach ( (array) $posted_field_ids as $field_id ) {
 
 					// Certain types of fields (checkboxes, multiselects) may come through empty. Save them as an empty array so that they don't get overwritten by the default on the next edit.
@@ -702,169 +696,55 @@ class BP_XProfile_User_Admin {
 		}
 
 		if ( bp_has_profile( $r ) ) :
-
 			while ( bp_profile_groups() ) : bp_the_profile_group(); ?>
+				<input type="hidden" name="field_ids[]" id="<?php echo esc_attr( 'field_ids_' . bp_get_the_profile_group_slug() ); ?>" value="<?php echo esc_attr( bp_get_the_profile_group_field_ids() ); ?>" />
 
-				<p class="description"><?php bp_the_profile_group_description(); ?></p>
-
-				<table class="form-table">
-					<tbody>
-
-					<?php while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
-
-						<tr>
-
-							<?php if ( 'textbox' === bp_get_the_profile_field_type() ) : ?>
+				<?php if ( bp_get_the_profile_group_description() ) : ?>
+					<p class="description"><?php bp_the_profile_group_description(); ?></p>
+				<?php
+				endif;
 
-								<th><label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label></th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<input type="text" name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" value="<?php bp_the_profile_field_edit_value(); ?>" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>/>
-									<span class="description"><?php bp_the_profile_field_description(); ?></span>
-								</td>
+				while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
 
-							<?php endif; ?>
-
-							<?php if ( 'textarea' === bp_get_the_profile_field_type() ) : ?>
+					<div<?php bp_field_css_class( 'bp-profile-field' ); ?>>
+						<?php
+						$field_type = bp_xprofile_create_field_type( bp_get_the_profile_field_type() );
+						$field_type->edit_field_html( array( 'user_id' => $r['user_id'] ) );
 
-								<th><label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label></th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<textarea rows="5" cols="40" name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>><?php bp_the_profile_field_edit_value(); ?></textarea>
-									<p class="description"><?php bp_the_profile_field_description(); ?></p>
-								</td>
-
-							<?php endif; ?>
+						if ( bp_get_the_profile_field_description() ) : ?>
+							<p class="description"><?php bp_the_profile_field_description(); ?></p>
+						<?php endif;
 
-							<?php if ( 'selectbox' === bp_get_the_profile_field_type() ) : ?>
+						do_action( 'bp_custom_profile_edit_fields_pre_visibility' );
+						$can_change_visibility = bp_current_user_can( 'bp_xprofile_change_field_visibility' );
+						?>
 
-								<th><label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label></th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<select name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-										<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], ) ); ?>
-									</select>
-									<span class="description"><?php bp_the_profile_field_description(); ?></span>
-								</td>
+						<p class="field-visibility-settings-<?php echo $can_change_visibility ? 'toggle' : 'notoggle'; ?>" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id(); ?>">
+							<?php
+							printf( __( 'This field can be seen by: <span class="%s">%s</span>', 'buddypress' ), esc_attr( 'current-visibility-level' ), bp_get_the_profile_field_visibility_level_label() );
 
+							if ( $can_change_visibility ) : ?>
+								 <a href="#" class="button visibility-toggle-link"><?php _e( 'Change', 'buddypress' ); ?></a>
 							<?php endif; ?>
+						</p>
+
+						<?php if ( $can_change_visibility ) : ?>
+							<div class="field-visibility-settings" id="field-visibility-settings-<?php bp_the_profile_field_id() ?>">
+								<fieldset>
+									<legend><?php _e( 'Who can see this field?', 'buddypress' ); ?></legend>
+									<?php bp_profile_visibility_radio_buttons(); ?>
+								</fieldset>
+								<a class="button field-visibility-settings-close" href="#"><?php _e( 'Close', 'buddypress' ); ?></a>
+							</div>
+						<?php endif;
 
-							<?php if ( 'multiselectbox' === bp_get_the_profile_field_type() ) : ?>
-
-								<th><label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label></th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<select name="<?php bp_the_profile_field_input_name(); ?>" id="<?php bp_the_profile_field_input_name(); ?>" multiple="multiple" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-										<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], ) ); ?>
-
-									</select>
-
-
-									<?php if ( !bp_get_the_profile_field_is_required() ) : ?>
-
-										<p><a class="clear-value" href="javascript:clear( '<?php bp_the_profile_field_input_name(); ?>' );"><?php _e( 'Clear', 'buddypress' ); ?></a></p>
-
-									<?php endif; ?>
-									<p class="description"><?php bp_the_profile_field_description(); ?></p>
-								</td>
-
-							<?php endif; ?>
-
-							<?php if ( 'radio' === bp_get_the_profile_field_type() ) : ?>
-
-								<th>
-									<span class="label"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></span>
-								</th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<fieldset>
-										<legend class="screen-reader-text"><span><?php bp_the_profile_field_name(); ?></span></legend>
-										<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], ) ); ?>
-									</fieldset>
-
-									<?php if ( !bp_get_the_profile_field_is_required() ) : ?>
-
-										<p><a class="clear-value" href="javascript:clear( '<?php bp_the_profile_field_input_name(); ?>' );"><?php _e( 'Clear', 'buddypress' ); ?></a></p>
-
-									<?php endif; ?>
-									<p class="description"><?php bp_the_profile_field_description(); ?></p>
-								</td>
-
-							<?php endif; ?>
-
-							<?php if ( 'checkbox' === bp_get_the_profile_field_type() ) : ?>
-
-								<th>
-									<span class="label"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></span>
-								</th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], ) ); ?>
-									<p class="description"><?php bp_the_profile_field_description(); ?></p>
-								</td>
-
-							<?php endif; ?>
-
-							<?php if ( 'datebox' === bp_get_the_profile_field_type() ) : ?>
-
-								<th>
-									<label for="<?php bp_the_profile_field_input_name(); ?>_day"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
-								</th>
-								<td class="admin-field-<?php bp_the_profile_field_type();?>">
-									<select name="<?php bp_the_profile_field_input_name(); ?>_day" id="<?php bp_the_profile_field_input_name(); ?>_day" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-										<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], 'type' => 'day', ) ); ?>
-
-									</select>
-
-									<select name="<?php bp_the_profile_field_input_name(); ?>_month" id="<?php bp_the_profile_field_input_name(); ?>_month" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-										<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], 'type' => 'month', ) ); ?>
-
-									</select>
-
-									<select name="<?php bp_the_profile_field_input_name(); ?>_year" id="<?php bp_the_profile_field_input_name(); ?>_year" <?php if ( bp_get_the_profile_field_is_required() ) : ?>aria-required="true"<?php endif; ?>>
-
-										<?php bp_the_profile_field_options( array( 'user_id' => $r['user_id'], 'type' => 'year', ) ); ?>
-
-									</select>
-									<p class="description"><?php bp_the_profile_field_description(); ?></p>
-								</td>
-
-							<?php endif; ?>
-
-						</tr>
-
-						<tr class="admin-field-visibility-tr">
-							<td class="admin-field-visibility-td">&nbsp;</td>
-							<td class="admin-field-visibility-td">
-
-								<?php do_action( 'bp_custom_profile_edit_fields_pre_visibility' ); ?>
-
-								<?php if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
-									<p class="description field-visibility-settings-toggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
-										<?php printf( __( 'This field can be seen by: <span class="current-visibility-level">%s</span>', 'buddypress' ), bp_get_the_profile_field_visibility_level_label() ) ?> <a href="#" class="visibility-toggle-link"><?php _e( 'Change', 'buddypress' ); ?></a>
-									</p>
-
-									<div class="field-visibility-settings" id="field-visibility-settings-<?php bp_the_profile_field_id() ?>">
-										<fieldset>
-											<legend><?php esc_html_e( 'Who can see this field?', 'buddypress' ) ?></legend>
-
-											<?php bp_profile_visibility_radio_buttons() ?>
-
-										</fieldset>
-										<a class="field-visibility-settings-close" href="#"><?php esc_html_e( 'Close', 'buddypress' ) ?></a>
-									</div>
-								<?php else : ?>
-									<div class="field-visibility-settings-notoggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
-										<?php printf( __( 'This field can be seen by: <span class="current-visibility-level">%s</span>', 'buddypress' ), bp_get_the_profile_field_visibility_level_label() ) ?>
-									</div>
-								<?php endif ?>
-
-							</td>
-						</tr>
+						do_action( 'bp_custom_profile_edit_fields' ); ?>
+					</div>
 
-					<?php endwhile; ?>
-					</tbody>
+				<?php
+				endwhile; // bp_profile_fields()
 
-				</table>
-				<input type="hidden" name="field_ids[]" id="field_ids_<?php bp_the_profile_group_slug(); ?>" value="<?php bp_the_profile_group_field_ids(); ?>" />
-			<?php endwhile;
+			endwhile; // bp_profile_groups()
 		endif;
 	}
 
diff --git a/bp-xprofile/bp-xprofile-classes.php b/bp-xprofile/bp-xprofile-classes.php
index 18e7724..4deff29 100644
--- a/bp-xprofile/bp-xprofile-classes.php
+++ b/bp-xprofile/bp-xprofile-classes.php
@@ -541,13 +541,25 @@ class BP_XProfile_Field {
 	public $default_visibility = 'public';
 	public $allow_custom_visibility = 'allowed';
 
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var BP_XProfile_Field_Type Field type object used for validation
+	 */
+	public $type_obj = null;
+
 	public $data;
 	public $message = null;
 	public $message_type = 'err';
 
 	public function __construct( $id = null, $user_id = null, $get_data = true ) {
-		if ( !empty( $id ) )
+		if ( !empty( $id ) ) {
 			$this->populate( $id, $user_id, $get_data );
+
+		// Initialise the type obj to prevent fatals when creating new profile fields
+		} else {
+			$this->type_obj            = bp_xprofile_create_field_type( 'textbox' );
+			$this->type_obj->field_obj = $this;
+		}
 	}
 
 	public function populate( $id, $user_id, $get_data ) {
@@ -573,6 +585,10 @@ class BP_XProfile_Field {
 			$this->order_by          = $field->order_by;
 			$this->is_default_option = $field->is_default_option;
 
+			// Create the field type and store a reference back to this object.
+			$this->type_obj            = bp_xprofile_create_field_type( $field->type );
+			$this->type_obj->field_obj = $this;
+
 			if ( $get_data && $user_id ) {
 				$this->data          = $this->get_field_data( $user_id );
 			}
@@ -618,6 +634,7 @@ class BP_XProfile_Field {
 		$this->order_by	   = apply_filters( 'xprofile_field_order_by_before_save',    $this->order_by,    $this->id );
 		$this->field_order = apply_filters( 'xprofile_field_field_order_before_save', $this->field_order, $this->id );
 		$this->can_delete  = apply_filters( 'xprofile_field_can_delete_before_save',  $this->can_delete,  $this->id );
+		$this->type_obj    = bp_xprofile_create_field_type( $this->type );
 
 		do_action_ref_array( 'xprofile_field_before_save', array( $this ) );
 
@@ -655,7 +672,7 @@ class BP_XProfile_Field {
 			 * Check to see if this is a field with child options.
 			 * We need to add the options to the db, if it is.
 			 */
-			if ( 'radio' == $this->type || 'selectbox' == $this->type || 'checkbox' == $this->type || 'multiselectbox' == $this->type ) {
+			if ( $this->type_obj->supports_options ) {
 
 				if ( !empty( $this->id ) ) {
 					$parent_id = $this->id;
@@ -663,34 +680,11 @@ class BP_XProfile_Field {
 					$parent_id = $wpdb->insert_id;
 				}
 
-				if ( 'radio' == $this->type ) {
-					$post_option  = !empty( $_POST['radio_option']           ) ? $_POST['radio_option']           : '';
-					$post_default = !empty( $_POST['isDefault_radio_option'] ) ? $_POST['isDefault_radio_option'] : '';
-
-					$options	= apply_filters( 'xprofile_field_options_before_save', $post_option,  'radio' );
-					$defaults	= apply_filters( 'xprofile_field_default_before_save', $post_default, 'radio' );
-
-				} elseif ( 'selectbox' == $this->type ) {
-					$post_option  = !empty( $_POST['selectbox_option']           ) ? $_POST['selectbox_option']           : '';
-					$post_default = !empty( $_POST['isDefault_selectbox_option'] ) ? $_POST['isDefault_selectbox_option'] : '';
-
-					$options	= apply_filters( 'xprofile_field_options_before_save', $post_option, 'selectbox' );
-					$defaults	= apply_filters( 'xprofile_field_default_before_save', $post_default, 'selectbox' );
-
-				} elseif ( 'multiselectbox' == $this->type ) {
-					$post_option  = !empty( $_POST['multiselectbox_option']           ) ? $_POST['multiselectbox_option']           : '';
-					$post_default = !empty( $_POST['isDefault_multiselectbox_option'] ) ? $_POST['isDefault_multiselectbox_option'] : '';
-
-					$options	= apply_filters( 'xprofile_field_options_before_save', $post_option, 'multiselectbox' );
-					$defaults	= apply_filters( 'xprofile_field_default_before_save', $post_default, 'multiselectbox' );
-
-				} elseif ( 'checkbox' == $this->type ) {
-					$post_option  = !empty( $_POST['checkbox_option']           ) ? $_POST['checkbox_option']           : '';
-					$post_default = !empty( $_POST['isDefault_checkbox_option'] ) ? $_POST['isDefault_checkbox_option'] : '';
-
-					$options	= apply_filters( 'xprofile_field_options_before_save', $post_option, 'checkbox' );
-					$defaults	= apply_filters( 'xprofile_field_default_before_save', $post_default, 'checkbox' );
-				}
+				// Allow plugins to filter the field's child options (i.e. the items in a selectbox).
+				$post_option  = ! empty( $_POST["{$this->type}_option"] ) ? $_POST["{$this->type}_option"] : '';
+				$post_default = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : '';
+				$options      = apply_filters( 'xprofile_field_options_before_save', $post_option,  $this->type );
+				$defaults     = apply_filters( 'xprofile_field_default_before_save', $post_default, $this->type );
 
 				$counter = 1;
 				if ( !empty( $options ) ) {
@@ -718,6 +712,10 @@ class BP_XProfile_Field {
 
 			do_action_ref_array( 'xprofile_field_after_save', array( $this ) );
 
+			// Recreate type_obj in case someone changed $this->type via a filter
+	 		$this->type_obj            = bp_xprofile_create_field_type( $this->type );
+	 		$this->type_obj->field_obj = $this;
+
 			return $field_id;
 		} else {
 			return false;
@@ -824,97 +822,14 @@ class BP_XProfile_Field {
 		return false;
 	}
 
-	/* ADMIN AREA HTML.
-	* TODO: Get this out of here and replace with standard template loops
-	*/
-
-	/* This function populates the items for radio buttons checkboxes and drop down boxes */
+	/**
+	 * This function populates the items for radio buttons checkboxes and drop down boxes
+	 */
 	public function render_admin_form_children() {
-		$input_types = array( 'checkbox', 'selectbox', 'multiselectbox', 'radio' );
-
-		foreach ( $input_types as $type ) {
-			$default_name = '';
-
-			if ( ( 'multiselectbox' == $type ) || ( 'checkbox' == $type ) ) {
-				$default_input = 'checkbox';
-			} else {
-				$default_input = 'radio';
-			}
-
-			$class = $this->type != $type ? 'display: none;' : '';
-
-			if ( empty( $this->default_visibility ) ) {
-				$this->default_visibility = 'public';
-			}
-
-			?>
-
-			<div id="<?php echo esc_attr( $type ); ?>" class="postbox bp-options-box" style="<?php echo esc_attr( $class ); ?> margin-top: 15px;">
-				<h3><?php _e( 'Please enter options for this Field:', 'buddypress' ); ?></h3>
-				<div class="inside">
-					<p>
-						<label for="sort_order_<?php echo esc_attr( $type ); ?>"><?php _e( 'Sort Order:', 'buddypress' ); ?></label>
-						<select name="sort_order_<?php echo esc_attr( $type ); ?>" id="sort_order_<?php echo esc_attr( $type ); ?>" >
-							<option value="custom" <?php selected( 'custom', $this->order_by ); ?>><?php _e( 'Custom',     'buddypress' ); ?></option>
-							<option value="asc"    <?php selected( 'asc',    $this->order_by ); ?>><?php _e( 'Ascending',  'buddypress' ); ?></option>
-							<option value="desc"   <?php selected( 'desc',   $this->order_by ); ?>><?php _e( 'Descending', 'buddypress' ); ?></option>
-						</select>
-					</p>
-
-					<?php if ( !$options = $this->get_children( true ) ) {
-
-						$i = 1;
-						while ( isset( $_POST[$type . '_option'][$i] ) ) {
-							(array) $options[] = (object) array(
-								'id'                => -1,
-								'name'              => $_POST[$type . '_option'][$i],
-								'is_default_option' => ( ( 'multiselectbox' != $type ) && ( 'checkbox' != $type ) && ( $_POST["isDefault_{$type}_option"] == $i ) ) ? 1 : $_POST["isDefault_{$type}_option"][$i]
-							);
-
-							++$i;
-						}
-					}
-
-					if ( !empty( $options ) ) {
-						for ( $i = 0, $count = count( $options ); $i < $count; ++$i ) {
-							$j = $i + 1;
-
-							if ( 'multiselectbox' == $type || 'checkbox' == $type )
-								$default_name = '[' . $j . ']'; ?>
-
-							<p class="sortable">
-								<span>&nbsp;&Xi;&nbsp;</span>
-								<input type="text" name="<?php echo esc_attr( $type ); ?>_option[<?php echo esc_attr( $j ); ?>]" id="<?php echo esc_attr( $type ); ?>_option<?php echo esc_attr( $j ); ?>" value="<?php echo stripslashes( esc_attr( $options[$i]->name ) ); ?>" />
-								<input type="<?php echo $default_input; ?>" name="isDefault_<?php echo esc_attr( $type ); ?>_option<?php echo esc_attr( $default_name ); ?>" <?php checked( (int) $options[$i]->is_default_option, true ); ?> value="<?php echo esc_attr( $j ); ?>" />
-								<span><?php _e( 'Default Value', 'buddypress' ); ?></span>
-								<a href="<?php echo esc_url( 'users.php?page=bp-profile-setup&amp;mode=delete_option&amp;option_id=' . $options[$i]->id ); ?>" class="ajax-option-delete" id="delete-<?php echo esc_attr( $options[$i]->id ); ?>">[x]</a>
-							</p>
-
-						<?php } /* end for */ ?>
-
-						<input type="hidden" name="<?php echo esc_attr( $type ); ?>_option_number" id="<?php echo esc_attr( $type ); ?>_option_number" value="<?php echo esc_attr( (int) $j + 1 ); ?>" />
-
-					<?php } else {
-
-						if ( 'multiselectbox' == $type || 'checkbox' == $type )
-							$default_name = '[1]'; ?>
-
-						<p class="sortable">
-							<span>&nbsp;&Xi;&nbsp;</span>
-							<input type="text" name="<?php echo esc_attr( $type ); ?>_option[1]" id="<?php echo esc_attr( $type ); ?>_option1" />
-							<input type="<?php echo esc_attr( $default_input ); ?>" name="isDefault_<?php echo esc_attr( $type ); ?>_option<?php echo esc_attr( $default_name ); ?>" id="isDefault_<?php echo esc_attr( $type ); ?>_option" value="1" />
-							<span><?php _e( 'Default Value', 'buddypress' ); ?></span>
-							<input type="hidden" name="<?php echo esc_attr( $type ); ?>_option_number" id="<?php echo esc_attr( $type ); ?>_option_number" value="2" />
-						</p>
-
-					<?php } /* end if */ ?>
-
-					<div id="<?php echo esc_attr( $type ); ?>_more"></div>
-					<p><a href="javascript:add_option('<?php echo esc_attr( $type ); ?>')"><?php _e( 'Add Another Option', 'buddypress' ); ?></a></p>
-				</div>
-			</div>
-
-		<?php }
+		foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) {
+			$type_obj = bp_xprofile_create_field_type( $field_type );
+			$type_obj->admin_new_field_html( $this );
+		}
 	}
 
 	public function render_admin_form( $message = '' ) {
@@ -1041,23 +956,15 @@ class BP_XProfile_Field {
 									<h3><label for="fieldtype"><?php _e( 'Field Type', 'buddypress'); ?></label></h3>
 									<div class="inside">
 										<select name="fieldtype" id="fieldtype" onchange="show_options(this.value)" style="width: 30%">
-											<optgroup label="<?php esc_attr_e( 'Single Fields', 'buddypress' ); ?>">
-												<option value="textbox"        <?php selected( $this->type, 'textbox'        ); ?>><?php _e( 'Text Box',             'buddypress' ); ?></option>
-												<option value="textarea"       <?php selected( $this->type, 'textarea'       ); ?>><?php _e( 'Multi-line Text Area', 'buddypress' ); ?></option>
-												<option value="datebox"        <?php selected( $this->type, 'datebox'        ); ?>><?php _e( 'Date Selector',        'buddypress' ); ?></option>
-											</optgroup>
-											<optgroup label="<?php esc_attr_e( 'Multi Fields', 'buddypress' ); ?>">
-												<option value="radio"          <?php selected( $this->type, 'radio'          ); ?>><?php _e( 'Radio Buttons',        'buddypress' ); ?></option>
-												<option value="selectbox"      <?php selected( $this->type, 'selectbox'      ); ?>><?php _e( 'Drop Down Select Box', 'buddypress' ); ?></option>
-												<option value="multiselectbox" <?php selected( $this->type, 'multiselectbox' ); ?>><?php _e( 'Multi Select Box',     'buddypress' ); ?></option>
-												<option value="checkbox"       <?php selected( $this->type, 'checkbox'       ); ?>><?php _e( 'Checkboxes',           'buddypress' ); ?></option>
-											</optgroup>
+											<?php bp_xprofile_admin_form_field_types( $this->type ); ?>
 										</select>
 
-										<?php do_action_ref_array( 'xprofile_field_additional_options', array( $this ) ); ?>
-
-										<?php $this->render_admin_form_children(); ?>
+										<?php
+										// Deprecated filter, don't use. Go look at {@link BP_XProfile_Field_Type::admin_new_field_html()}.
+										do_action( 'xprofile_field_additional_options', $this );
 
+										$this->render_admin_form_children();
+										?>
 									</div>
 								</div>
 
@@ -1086,25 +993,21 @@ class BP_XProfile_Field {
 		if ( '' == $_POST['title'] || '' == $_POST['required'] || '' == $_POST['fieldtype'] ) {
 			$message = __( 'Please make sure you fill out all required fields.', 'buddypress' );
 			return false;
-		} else if ( empty( $_POST['field_file'] ) && $_POST['fieldtype'] == 'radio' && empty( $_POST['radio_option'][1] ) ) {
-			$message = __( 'Radio button field types require at least one option. Please add options below.', 'buddypress' );
-			return false;
-		} else if ( empty( $_POST['field_file'] ) && $_POST['fieldtype'] == 'selectbox' && empty( $_POST['selectbox_option'][1] ) ) {
-			$message = __( 'Select box field types require at least one option. Please add options below.', 'buddypress' );
-			return false;
-		} else if ( empty( $_POST['field_file'] ) && $_POST['fieldtype'] == 'multiselectbox' && empty( $_POST['multiselectbox_option'][1] ) ) {
-			$message = __( 'Select box field types require at least one option. Please add options below.', 'buddypress' );
-			return false;
-		} else if ( empty( $_POST['field_file'] ) && $_POST['fieldtype'] == 'checkbox' && empty( $_POST['checkbox_option'][1] ) ) {
-			$message = __( 'Checkbox field types require at least one option. Please add options below.', 'buddypress' );
-			return false;
-		} else {
-			return true;
+
+		} elseif ( empty( $_POST['field_file'] ) ) {
+			$field_type  = bp_xprofile_create_field_type( $_POST['fieldtype'] );
+			$option_name = "{$_POST['fieldtype']}_option";
+
+			if ( $field_type->supports_options && isset( $_POST[$option_name] ) && empty( $_POST[$option_name][1] ) ) {
+				$message = __( 'This field type require at least one option. Please add options below.', 'buddypress' );
+				return false;
+			} 
 		}
+
+		return true;
 	}
 }
 
-
 class BP_XProfile_ProfileData {
 	public $id;
 	public $user_id;
@@ -1540,3 +1443,1385 @@ class BP_XProfile_ProfileData {
 		return $data[$field_name];
 	}
 }
+
+/**
+ * Datebox xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Datebox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the datebox field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Date Selector', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^\d{4}-\d{1,2}-\d{1,2} 00:00:00$/', 'replace' );  // "Y-m-d 00:00:00"
+		do_action( 'bp_xprofile_field_type_datebox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+		$user_id = bp_displayed_user_id();
+
+		// user_id is a special optional parameter that we pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		}
+
+		$day_html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'id'   => bp_get_the_profile_field_input_name() . '_day',
+				'name' => bp_get_the_profile_field_input_name() . '_day',
+			),
+			$raw_properties
+		) );
+
+		$month_html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'id'   => bp_get_the_profile_field_input_name() . '_month',
+				'name' => bp_get_the_profile_field_input_name() . '_month',
+			),
+			$raw_properties
+		) );
+
+		$year_html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'id'   => bp_get_the_profile_field_input_name() . '_year',
+				'name' => bp_get_the_profile_field_input_name() . '_year',
+			),
+			$raw_properties
+		) );
+	?>
+		<div class="datebox">
+
+			<label for="<?php bp_the_profile_field_input_name(); ?>_day"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+
+			<select <?php echo $day_html; ?>>
+				<?php bp_the_profile_field_options( array( 'type' => 'day', 'user_id' => $user_id ) ); ?>
+			</select>
+
+			<select <?php echo $month_html; ?>>
+				<?php bp_the_profile_field_options( array( 'type' => 'month', 'user_id' => $user_id ) ); ?>
+			</select>
+
+			<select <?php echo $year_html; ?>>
+				<?php bp_the_profile_field_options( array( 'type' => 'year', 'user_id' => $user_id ) ); ?>
+			</select>
+
+		</div>
+	<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled seperately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$options = $this->field_obj->get_children();
+		$date    = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] );
+
+		$day   = 0;
+		$month = 0;
+		$year  = 0;
+		$html  = '';
+
+		// Set day, month, year defaults
+		if ( ! empty( $date ) ) {
+
+			// If Unix timestamp
+			if ( is_numeric( $date ) ) {
+				$day   = date( 'j', $date );
+				$month = date( 'F', $date );
+				$year  = date( 'Y', $date );
+
+			// If MySQL timestamp
+			} else {
+				$day   = mysql2date( 'j', $date );
+				$month = mysql2date( 'F', $date, false ); // Not localized, so that selected() works below
+				$year  = mysql2date( 'Y', $date );
+			}
+		}
+
+		// Check for updated posted values, and errors preventing them from being saved first time.
+		if ( ! empty( $_POST['field_' . $this->field_obj->id . '_day'] ) ) {
+			$new_day = (int) $_POST['field_' . $this->field_obj->id . '_day'];
+			$day     = ( $day != $new_day ) ? $new_day : $day;
+		}
+
+		if ( ! empty( $_POST['field_' . $this->field_obj->id . '_month'] ) ) {
+			$new_month = (int) $_POST['field_' . $this->field_obj->id . '_month'];
+			$month     = ( $month != $new_month ) ? $new_month : $month;
+		}
+
+		if ( ! empty( $_POST['field_' . $this->field_obj->id . '_year'] ) ) {
+			$new_year = date( 'j', (int) $_POST['field_' . $this->field_obj->id . '_year'] );
+			$year     = ( $year != $new_year ) ? $new_year : $year;
+		}
+
+		// $type will be passed by calling function when needed
+		switch ( $args['type'] ) {
+			case 'day':
+				$html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $day, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) );
+
+				for ( $i = 1; $i < 32; ++$i ) {
+					$html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', (int) $i, selected( $day, $i, false ), (int) $i );
+				}
+			break;
+
+			case 'month':
+				$eng_months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' );
+
+				$months = array(
+					__( 'January', 'buddypress' ),
+					__( 'February', 'buddypress' ),
+					__( 'March', 'buddypress' ),
+					__( 'April', 'buddypress' ),
+					__( 'May', 'buddypress' ),
+					__( 'June', 'buddypress' ),
+					__( 'July', 'buddypress' ),
+					__( 'August', 'buddypress' ),
+					__( 'September', 'buddypress' ),
+					__( 'October', 'buddypress' ),
+					__( 'November', 'buddypress' ),
+					__( 'December', 'buddypress' )
+				);
+
+				$html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $month, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) );
+
+				for ( $i = 0; $i < 12; ++$i ) {
+					$html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $eng_months[$i] ), selected( $month, $eng_months[$i], false ), $months[$i] );
+				}
+			break;
+
+			case 'year':
+				$html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $year, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) );
+
+				for ( $i = 2037; $i > 1901; $i-- ) {
+					$html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', (int) $i, selected( $year, $i, false ), (int) $i );
+				}
+			break;
+		}
+
+		echo apply_filters( 'bp_get_the_profile_field_datebox', $html, $args['type'], $day, $month, $year, $this->field_obj->id, $date );
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$day_html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'id'   => bp_get_the_profile_field_input_name() . '_day',
+				'name' => bp_get_the_profile_field_input_name() . '_day',
+			),
+			$raw_properties
+		) );
+
+		$month_html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'id'   => bp_get_the_profile_field_input_name() . '_month',
+				'name' => bp_get_the_profile_field_input_name() . '_month',
+			),
+			$raw_properties
+		) );
+
+		$year_html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'id'   => bp_get_the_profile_field_input_name() . '_year',
+				'name' => bp_get_the_profile_field_input_name() . '_year',
+			),
+			$raw_properties
+		) );
+	?>
+		<select <?php echo $day_html; ?>>
+			<?php bp_the_profile_field_options( 'type=day' ); ?>
+		</select>
+
+		<select <?php echo $month_html; ?>>
+			<?php bp_the_profile_field_options( 'type=month' ); ?>
+		</select>
+
+		<select <?php echo $year_html; ?>>
+			<?php bp_the_profile_field_options( 'type=year' ); ?>
+		</select>
+	<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
+
+/**
+ * Checkbox xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Checkbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the checkbox field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Checkboxes', 'xprofile field type', 'buddypress' );
+
+		$this->supports_multiple_defaults = true;
+		$this->accepts_null_value         = true;
+		$this->supports_options           = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+		do_action( 'bp_xprofile_field_type_checkbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.checkbox.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+		$user_id = bp_displayed_user_id();
+
+		// user_id is a special optional parameter that we pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		}
+	?>
+		<div class="checkbox">
+
+			<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
+
+		</div>
+		<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled seperately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$options       = $this->field_obj->get_children();
+		$option_values = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] );
+		$option_values = (array) maybe_unserialize( $option_values );
+
+		$html = '';
+
+		// Check for updated posted values, but errors preventing them from being saved first time
+		if ( isset( $_POST['field_' . $this->field_obj->id] ) && $option_values != maybe_serialize( $_POST['field_' . $this->field_obj->id] ) ) {
+			if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+				$option_values = array_map( 'sanitize_text_field', $_POST['field_' . $this->field_obj->id] );
+			}
+		}
+
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+			$selected = '';
+
+			// First, check to see whether the user's saved values match the option
+			for ( $j = 0, $count_values = count( $option_values ); $j < $count_values; ++$j ) {
+
+				// Run the allowed option name through the before_save filter, so we'll be sure to get a match
+				$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+
+				if ( $option_values[$j] === $allowed_options || in_array( $allowed_options, $option_values ) ) {
+					$selected = ' checked="checked"';
+					break;
+				}
+			}
+
+			// If the user has not yet supplied a value for this field, check to see whether there is a default value available
+			if ( ! is_array( $option_values ) && empty( $option_values ) && empty( $selected ) && ! empty( $options[$k]->is_default_option ) ) {
+				$selected = ' checked="checked"';
+			}
+
+			$new_html = sprintf( '<label><input %1$s type="checkbox" name="%2$s" id="%3$s" value="%4$s">%5$s</label>',
+				$selected,
+				esc_attr( "field_{$this->field_obj->id}[]" ),
+				esc_attr( "field_{$options[$k]->id}_{$k}" ),
+				esc_attr( stripslashes( $options[$k]->name ) ),
+				esc_html( stripslashes( $options[$k]->name ) )
+			);
+			$html .= apply_filters( 'bp_get_the_profile_field_options_checkbox', $new_html, $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html;
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		bp_the_profile_field_options();
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'checkbox' );
+	}
+}
+
+/**
+ * Radio button xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Radiobutton extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the radio button field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Radio Buttons', 'xprofile field type', 'buddypress' );
+
+		$this->supports_options = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+		do_action( 'bp_xprofile_field_type_radiobutton', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.radio.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+		$user_id = bp_displayed_user_id();
+
+		// user_id is a special optional parameter that we pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		}
+	?>
+		<div class="radio">
+
+			<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+			<?php bp_the_profile_field_options( "user_id={$user_id}" );
+
+			if ( ! bp_get_the_profile_field_is_required() ) : ?>
+				<a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>' );"><?php esc_html_e( 'Clear', 'buddypress' ); ?></a>
+			<?php endif; ?>
+
+		</div>
+		<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled seperately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$option_value = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] );
+		$options      = $this->field_obj->get_children();
+
+		$html = sprintf( '<div id="%s">', esc_attr( 'field_' . $this->field_obj->id ) );
+
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+
+			// Check for updated posted values, but errors preventing them from being saved first time
+			if ( isset( $_POST['field_' . $this->field_obj->id] ) && $option_value != $_POST['field_' . $this->field_obj->id] ) {
+				if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+					$option_value = sanitize_text_field( $_POST['field_' . $this->field_obj->id] );
+				}
+			}
+
+			// Run the allowed option name through the before_save filter, so we'll be sure to get a match
+			$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+			$selected        = '';
+
+			if ( $option_value === $allowed_options || ( empty( $option_value ) && ! empty( $options[$k]->is_default_option ) ) ) {
+				$selected = ' checked="checked"';
+			}
+
+			$new_html = sprintf( '<label><input %1$s type="radio" name="%2$s" id="%3$s" value="%4$s">%5$s</label>',
+				$selected,
+				esc_attr( "field_{$this->field_obj->id}" ),
+				esc_attr( "option_{$options[$k]->id}" ),
+				esc_attr( stripslashes( $options[$k]->name ) ),
+				esc_html( stripslashes( $options[$k]->name ) )
+			);
+			$html .= apply_filters( 'bp_get_the_profile_field_options_radio', $new_html, $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html . '</div>';
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		bp_the_profile_field_options();
+
+		if ( ! bp_get_the_profile_field_is_required() ) : ?>
+			<a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>' );"><?php esc_html_e( 'Clear', 'buddypress' ); ?></a>
+		<?php endif; ?>
+	<?php
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'radio' );
+	}
+}
+
+/**
+ * Multi-selectbox xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Multiselectbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the multi-selectbox field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Multi Select Box', 'xprofile field type', 'buddypress' );
+
+		$this->supports_multiple_defaults = true;
+		$this->accepts_null_value         = true;
+		$this->supports_options           = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+		do_action( 'bp_xprofile_field_type_multiselectbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/select.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+		$user_id = bp_displayed_user_id();
+
+		// user_id is a special optional parameter that we pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		}
+
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'multiple' => 'multiple',
+				'id'       => bp_get_the_profile_field_input_name() . '[]',
+				'name'     => bp_get_the_profile_field_input_name() . '[]',
+			),
+			$raw_properties
+		) );
+	?>
+		<label for="<?php bp_the_profile_field_input_name(); ?>[]"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php _e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+		<select <?php echo $html; ?>>
+			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
+		</select>
+
+		<?php if ( ! bp_get_the_profile_field_is_required() ) : ?>
+			<a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>[]' );"><?php esc_html_e( 'Clear', 'buddypress' ); ?></a>
+		<?php endif; ?>
+	<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled seperately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) );
+
+		$options = $this->field_obj->get_children();
+		$html    = '';
+
+		if ( empty( $original_option_values ) && ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+			$original_option_values = sanitize_text_field( $_POST['field_' . $this->field_obj->id] );
+		}
+
+		$option_values = (array) $original_option_values;
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+			$selected = '';
+
+			// Check for updated posted values, but errors preventing them from being saved first time
+			foreach( $option_values as $i => $option_value ) {
+				if ( isset( $_POST['field_' . $this->field_obj->id] ) && $_POST['field_' . $this->field_obj->id][$i] != $option_value ) {
+					if ( ! empty( $_POST['field_' . $this->field_obj->id][$i] ) ) {
+						$option_values[] = sanitize_text_field( $_POST['field_' . $this->field_obj->id][$i] );
+					}
+				}
+			}
+
+			// Run the allowed option name through the before_save filter, so we'll be sure to get a match
+			$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+
+			// First, check to see whether the user-entered value matches
+			if ( in_array( $allowed_options, $option_values ) ) {
+				$selected = ' selected="selected"';
+			}
+
+			// Then, if the user has not provided a value, check for defaults
+			if ( ! is_array( $original_option_values ) && empty( $option_values ) && ! empty( $options[$k]->is_default_option ) ) {
+				$selected = ' selected="selected"';
+			}
+
+			$html .= apply_filters( 'bp_get_the_profile_field_options_multiselect', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_html( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html;
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array( 'multiple' => 'multiple' ),
+			$raw_properties
+		) );
+	?>
+		<select <?php echo $html; ?>>
+			<?php bp_the_profile_field_options(); ?>
+		</select>
+	<?php
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'checkbox' );
+	}
+}
+
+/**
+ * Selectbox xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Selectbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the selectbox field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Drop Down Select Box', 'xprofile field type', 'buddypress' );
+
+		$this->supports_options = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+		do_action( 'bp_xprofile_field_type_selectbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/select.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+		$user_id = bp_displayed_user_id();
+
+		// user_id is a special optional parameter that we pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		}
+
+		$html = $this->get_edit_field_html_elements( $raw_properties );
+	?>
+		<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+		<select <?php echo $html; ?>>
+			<?php bp_the_profile_field_options( "user_id={$user_id}" ); ?>
+		</select>
+	<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled seperately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) );
+
+		$options = $this->field_obj->get_children();
+		$html     = '<option value="">' . /* translators: no option picked in select box */ esc_html__( '----', 'buddypress' ) . '</option>';
+
+		if ( empty( $original_option_values ) && !empty( $_POST['field_' . $this->field_obj->id] ) ) {
+			$original_option_values = sanitize_text_field(  $_POST['field_' . $this->field_obj->id] );
+		}
+
+		$option_values = (array) $original_option_values;
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+			$selected = '';
+
+			// Check for updated posted values, but errors preventing them from being saved first time
+			foreach( $option_values as $i => $option_value ) {
+				if ( isset( $_POST['field_' . $this->field_obj->id] ) && $_POST['field_' . $this->field_obj->id] != $option_value ) {
+					if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+						$option_values[$i] = sanitize_text_field( $_POST['field_' . $this->field_obj->id] );
+					}
+				}
+			}
+
+			// Run the allowed option name through the before_save filter, so we'll be sure to get a match
+			$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+
+			// First, check to see whether the user-entered value matches
+			if ( in_array( $allowed_options, $option_values ) ) {
+				$selected = ' selected="selected"';
+			}
+
+			// Then, if the user has not provided a value, check for defaults
+			if ( ! is_array( $original_option_values ) && empty( $option_values ) && $options[$k]->is_default_option ) {
+				$selected = ' selected="selected"';
+			}
+
+			$html .= apply_filters( 'bp_get_the_profile_field_options_select', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_html( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html;
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$html = $this->get_edit_field_html_elements( $raw_properties );
+	?>
+		<select <?php echo $html; ?>>
+			<?php bp_the_profile_field_options(); ?>
+		</select>
+	<?php
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'radio' );
+	}
+}
+
+/**
+ * Textarea xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Textarea extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the textarea field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Multi-line Text Area', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^.*$/m', 'replace' );
+		do_action( 'bp_xprofile_field_type_textarea', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/textarea.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// user_id is a special optional parameter that certain other fields types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'cols' => 40,
+				'rows' => 5,
+			),
+			$raw_properties
+		) );
+	?>
+		<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+		<textarea <?php echo $html; ?>><?php bp_the_profile_field_edit_value(); ?></textarea>
+	<?php
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'cols' => 40,
+				'rows' => 5,
+			),
+			$raw_properties
+		) );
+	?>
+		<textarea <?php echo $html; ?>></textarea>
+	<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
+
+/**
+ * Textbox xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Textbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the textbox field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Text Box', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^.*$/', 'replace' );
+		do_action( 'bp_xprofile_field_type_textbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.text.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// user_id is a special optional parameter that certain other fields types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'type'  => 'text',
+				'value' => bp_get_the_profile_field_edit_value(),
+			),
+			$raw_properties
+		) );
+	?>
+		<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+		<input <?php echo $html; ?>>
+	<?php
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array( 'type' => 'text' ),
+			$raw_properties
+		) );
+	?>
+		<input <?php echo $html; ?>>
+	<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
+
+/**
+ * Number xprofile field type.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+class BP_XProfile_Field_Type_Number extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the number field type
+	 *
+	 * @since BuddyPress (2.0.0)
+ 	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Number', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^\d+$/', 'replace' );
+		do_action( 'bp_xprofile_field_type_number', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of {@link http://dev.w3.org/html5/markup/input.number.html permitted attributes} that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// user_id is a special optional parameter that certain other fields types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array(
+				'type'  => 'number',
+				'value' => bp_get_the_profile_field_edit_value(),
+			),
+			$raw_properties
+		) );
+	?>
+		<label for="<?php bp_the_profile_field_input_name(); ?>"><?php bp_the_profile_field_name(); ?> <?php if ( bp_get_the_profile_field_is_required() ) : ?><?php esc_html_e( '(required)', 'buddypress' ); ?><?php endif; ?></label>
+		<input <?php echo $html; ?>>
+	<?php
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$html = $this->get_edit_field_html_elements( array_merge(
+			array( 'type' => 'number' ),
+			$raw_properties
+		) );
+	?>
+		<input <?php echo $html; ?>>
+	<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
+
+/**
+ * Represents a type of XProfile field and holds meta information about the type of value that it accepts.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+abstract class BP_XProfile_Field_Type {
+
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var array Field type validation regexes
+	 */
+	protected $validation_regex = array();
+
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var array Field type whitelisted values
+	 */
+	protected $validation_whitelist = array();
+
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var string The name of this field type
+	 */
+	public $name = '';
+
+	/**
+	 * The name of the category that this field type should be grouped with. Used on the [Users > Profile Fields] screen in wp-admin.
+	 *
+	 * @since BuddyPress (2.0.0)
+	 * @var string
+	 */
+	public $category = '';
+
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var bool If this is set, allow BP to store null/empty values for this field type.
+	 */
+	public $accepts_null_value = false;
+
+	/**
+	 * If this is set, BP will set this field type's validation whitelist from the field's options (e.g checkbox, selectbox).
+	 *
+	 * @since BuddyPress (2.0.0)
+	 * @var bool Does this field support options? e.g. selectbox, radio buttons, etc.
+	 */
+	public $supports_options = false;
+
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var bool Does this field type support multiple options being set as default values? e.g. multiselectbox, checkbox.
+	 */
+	public $supports_multiple_defaults = false;
+
+	/**
+	 * @since BuddyPress (2.0.0)
+	 * @var BP_XProfile_Field If this object is created by instantiating a {@link BP_XProfile_Field}, this is a reference back to that object.
+	 */
+	public $field_obj = null;
+
+	/**
+	 * Constructor
+	 *
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function __construct() {
+		do_action( 'bp_xprofile_field_type', $this );
+	}
+
+	/**
+	 * Set a regex that profile data will be asserted against.
+	 * 
+	 * You can call this method multiple times to set multiple formats. When validation is performed,
+	 * it's successful as long as the new value matches any one of the registered formats.
+	 * 
+	 * @param string $format Regex string
+	 * @param string $replace_format Optional; if 'replace', replaces the format instead of adding to it. Defaults to 'add'.
+	 * @return BP_XProfile_Field_Type
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function set_format( $format, $replace_format = 'add' ) {
+
+		$format = apply_filters( 'bp_xprofile_field_type_set_format', $format, $replace_format, $this );
+
+		if ( 'add' === $replace_format ) {
+			$this->validation_regex[] = $format;
+		} elseif ( 'replace' === $replace_format ) {
+			$this->validation_regex = array( $format );
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add a value to this type's whitelist that that profile data will be asserted against.
+	 * 
+	 * You can call this method multiple times to set multiple formats. When validation is performed,
+	 * it's successful as long as the new value matches any one of the registered formats.
+	 * 
+	 * @param string|array $values
+	 * @return BP_XProfile_Field_Type
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function set_whitelist_values( $values ) {
+		foreach ( (array) $values as $value ) {
+			$this->validation_whitelist[] = apply_filters( 'bp_xprofile_field_type_set_whitelist_values', $value, $values, $this );
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Check the given string against the registered formats for this field type.
+	 *
+	 * This method doesn't support chaining.
+	 *
+	 * @param string|array $values Value to check against the registered formats
+	 * @return bool True if the value validates
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function is_valid( $values ) {
+		$validated = false;
+
+		// Some types of field (e.g. multi-selectbox) may have multiple values to check
+		foreach ( (array) $values as $value ) {
+
+			// Validate the $value against the type's accepted format(s).
+			foreach ( $this->validation_regex as $format ) {
+				if ( 1 === preg_match( $format, $value ) ) {
+					$validated = true;
+					continue;
+
+				} else {
+					$validated = false;
+				}
+			}
+		}
+
+		// Handle field types with accepts_null_value set if $values is an empty array
+		if ( ! $validated && is_array( $values ) && empty( $values ) && $this->accepts_null_value ) {
+			$validated = true;
+		}
+
+		// If there's a whitelist set, also check the $value.
+		if ( $validated && ! empty( $values ) && ! empty( $this->validation_whitelist ) ) {
+
+			foreach ( (array) $values as $value ) {
+				$validated = in_array( $value, $this->validation_whitelist, true );
+			}
+		}
+
+		return (bool) apply_filters( 'bp_xprofile_field_type_is_valid', $validated, $values, $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	abstract public function edit_field_html( array $raw_properties = array() );
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 * Populate this method in a child class if it's required. Otherwise, you can leave it out.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function edit_field_options_html( array $args = array() ) {}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since BuddyPress (2.0.0)
+	 */
+	abstract public function admin_field_html( array $raw_properties = array() );
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * You don't need to implement this method for all field types. It's used in core by the
+	 * selectbox, multi selectbox, checkbox, and radio button fields, to allow the admin to
+	 * enter the child option values (e.g. the choices in a select box).
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string $control_type Optional. HTML input type used to render the current field's child options.
+	 * @since BuddyPress (2.0.0)
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		$type = array_search( get_class( $this ), bp_xprofile_get_field_types() );
+		if ( false === $type ) {
+			return;
+		}
+
+		$class            = $current_field->type != $type ? 'display: none;' : '';
+		$current_type_obj = bp_xprofile_create_field_type( $type );
+		?>
+
+		<div id="<?php echo esc_attr( $type ); ?>" class="postbox bp-options-box" style="<?php echo esc_attr( $class ); ?> margin-top: 15px;">
+			<h3><?php esc_html_e( 'Please enter options for this Field:', 'buddypress' ); ?></h3>
+			<div class="inside">
+				<p>
+					<label for="sort_order_<?php echo esc_attr( $type ); ?>"><?php esc_html_e( 'Sort Order:', 'buddypress' ); ?></label>
+					<select name="sort_order_<?php echo esc_attr( $type ); ?>" id="sort_order_<?php echo esc_attr( $type ); ?>" >
+						<option value="custom" <?php selected( 'custom', $current_field->order_by ); ?>><?php esc_html_e( 'Custom',     'buddypress' ); ?></option>
+						<option value="asc"    <?php selected( 'asc',    $current_field->order_by ); ?>><?php esc_html_e( 'Ascending',  'buddypress' ); ?></option>
+						<option value="desc"   <?php selected( 'desc',   $current_field->order_by ); ?>><?php esc_html_e( 'Descending', 'buddypress' ); ?></option>
+					</select>
+				</p>
+
+				<?php
+				$options = $current_field->get_children( true );
+
+				// If no children options exists for this field, check in $_POST for a submitted form (e.g. on the "new field" screen).
+				if ( ! $options ) {
+
+					$options = array();
+					$i       = 1;
+
+					while ( isset( $_POST[$type . '_option'][$i] ) ) {
+
+						// Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE.
+						if ( $current_type_obj->supports_options && ! $current_type_obj->supports_multiple_defaults && isset( $_POST["isDefault_{$type}_option"][$i] ) && (int) $_POST["isDefault_{$type}_option"] === $i ) {
+							$is_default_option = true;
+						} elseif ( isset( $_POST["isDefault_{$type}_option"][$i] ) ) {
+							$is_default_option = (bool) $_POST["isDefault_{$type}_option"][$i];
+						} else {
+							$is_default_option = false;
+						}
+
+						// Grab the values from $_POST to use as the form's options
+						$options[] = (object) array(
+							'id'                => -1,
+							'is_default_option' => $is_default_option,
+							'name'              => sanitize_text_field( stripslashes( $_POST[$type . '_option'][$i] ) ),
+						);
+
+						++$i;
+					}
+
+					// If there are still no children options set, this must be the "new field" screen, so add one new/empty option.
+					if ( ! $options ) {
+						$options[] = (object) array(
+							'id'                => -1,
+							'is_default_option' => false,
+							'name'              => '',
+						);
+					}
+				}
+
+				// Render the markup for the children options
+				if ( ! empty( $options ) ) {
+					$default_name = '';
+
+					for ( $i = 0, $count = count( $options ); $i < $count; ++$i ) :
+						$j = $i + 1;
+
+						// Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE.
+						if ( $current_type_obj->supports_options && $current_type_obj->supports_multiple_defaults ) {
+							$default_name = '[' . $j . ']';
+						}
+						?>
+
+						<p class="sortable">
+							<span>&nbsp;&Xi;&nbsp;</span>
+							<input type="text" name="<?php echo esc_attr( "{$type}_option[{$j}]" ); ?>" id="<?php echo esc_attr( "{$type}_option{$j}" ); ?>" value="<?php echo esc_attr( $options[$i]->name ); ?>" />
+							<input type="<?php echo esc_attr( $control_type ); ?>" name="<?php echo esc_attr( "isDefault_{$type}_option{$default_name}" ); ?>" <?php checked( $options[$i]->is_default_option, true ); ?> value="<?php echo esc_attr( $j ); ?>" />
+							<span><?php _e( 'Default Value', 'buddypress' ); ?></span>
+						</p>
+					<?php endfor; ?>
+
+					<input type="hidden" name="<?php echo esc_attr( "{$type}_option_number" ); ?>" id="<?php echo esc_attr( "{$type}_option_number" ); ?>" value="<?php echo esc_attr( $j + 1 ); ?>" />
+				<?php } ?>
+
+				<div id="<?php echo esc_attr( "{$type}_more" ); ?>"></div>
+				<p><a href="javascript:add_option('<?php echo esc_js( $type ); ?>')"><?php esc_html_e( 'Add Another Option', 'buddypress' ); ?></a></p>
+			</div>
+		</div>
+
+		<?php
+	}
+
+
+	/**
+	 * Internal protected/private helper methods past this point.
+	 */
+
+	/**
+	 * Get a sanitised and escaped string of the edit field's HTML elements and attributes.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 * This method was intended to be static but couldn't be because php.net/lsb/ requires PHP >= 5.3.
+	 *
+	 * @param array $properties Optional key/value array of attributes for this edit field.
+	 * @return string
+	 * @since BuddyPress (2.0.0)
+	 */
+	protected function get_edit_field_html_elements( array $properties = array() ) {
+
+		$properties = array_merge( array(
+			'id'   => bp_get_the_profile_field_input_name(),
+			'name' => bp_get_the_profile_field_input_name(),
+		), $properties );
+
+		if ( bp_get_the_profile_field_is_required() ) {
+			$properties['aria-required'] = 'true';
+		}
+
+		$html       = '';
+		$properties = (array) apply_filters( 'bp_xprofile_field_edit_html_elements', $properties, get_class( $this ) );
+
+		foreach ( $properties as $name => $value ) {
+			$html .= sprintf( '%s="%s" ', sanitize_key( $name ), esc_attr( $value ) );
+		}
+
+		return $html;
+	}
+}
diff --git a/bp-xprofile/bp-xprofile-functions.php b/bp-xprofile/bp-xprofile-functions.php
index 4c508dc..2df82cd 100644
--- a/bp-xprofile/bp-xprofile-functions.php
+++ b/bp-xprofile/bp-xprofile-functions.php
@@ -60,6 +60,51 @@ function xprofile_update_field_group_position( $field_group_id, $position ) {
 
 /*** Field Management *********************************************************/
 
+/**
+ * Get details of all xprofile field types.
+ *
+ * @return array Key/value pairs (field type => class name).
+ * @since BuddyPress (2.0.0)
+ */
+function bp_xprofile_get_field_types() {
+	$fields = array(
+		'checkbox'       => 'BP_XProfile_Field_Type_Checkbox',
+		'datebox'        => 'BP_XProfile_Field_Type_Datebox',
+		'multiselectbox' => 'BP_XProfile_Field_Type_Multiselectbox',
+		'number'         => 'BP_XProfile_Field_Type_Number',
+		'radio'          => 'BP_XProfile_Field_Type_Radiobutton',
+		'selectbox'      => 'BP_XProfile_Field_Type_Selectbox',
+		'textarea'       => 'BP_XProfile_Field_Type_Textarea',
+		'textbox'        => 'BP_XProfile_Field_Type_Textbox',
+	);
+
+	// If you've added a custom field type in a plugin, register it with this filter.
+	return apply_filters( 'bp_xprofile_get_field_types', $fields );
+}
+
+/**
+ * Creates the specified field type object; used for validation and templating.
+ *
+ * @param string $type Type of profile field to create. See {@link bp_xprofile_get_field_types()} for default core values.
+ * @return object If field type unknown, returns BP_XProfile_Field_Type_Textarea. Otherwise returns an instance of the relevant child class of BP_XProfile_Field_Type.
+ * @since BuddyPress (2.0.0)
+ */
+function bp_xprofile_create_field_type( $type ) {
+
+	$field = bp_xprofile_get_field_types();
+	$class = isset( $field[$type] ) ? $field[$type] : '';
+
+	/**
+	 * For backpat and to handle (missing) field types introduced by other plugins, fallback to
+	 * textbox if a type is unknown. Textbox validation and display is intentionally low key.
+	 */
+	if ( $class && class_exists( $class ) ) {
+		return new $class;
+	} else {
+		return new BP_XProfile_Field_Type_Textbox;
+	}
+}
+
 function xprofile_insert_field( $args = '' ) {
 	global $bp;
 
@@ -214,38 +259,23 @@ function xprofile_set_field_data( $field, $user_id, $value, $is_required = false
 	if ( $is_required && ( empty( $value ) || !is_array( $value ) && !strlen( trim( $value ) ) ) )
 		return false;
 
-	$field = new BP_XProfile_Field( $field_id );
+	$field          = new BP_XProfile_Field( $field_id );
+	$field_type_obj = bp_xprofile_create_field_type( BP_XProfile_Field::get_type( $field_id ) );
 
-	// If the value is empty, then delete any field data that exists, unless the field is of a
-	// type where null values are semantically meaningful
-	if ( empty( $value ) && 'checkbox' != $field->type && 'multiselectbox' != $field->type ) {
+	// If the value is empty, then delete any field data that exists, unless the field is of a type where null values are semantically meaningful
+	if ( empty( $value ) && ! $field_type_obj->accepts_null_value ) {
 		xprofile_delete_field_data( $field_id, $user_id );
 		return true;
 	}
 
-	$possible_values = array();
-
-	// Check the value is an acceptable value
-	if ( 'checkbox' == $field->type || 'radio' == $field->type || 'selectbox' == $field->type || 'multiselectbox' == $field->type ) {
-		$options = $field->get_children();
-
-		foreach( $options as $option )
-			$possible_values[] = $option->name;
-
-		if ( is_array( $value ) ) {
-			foreach( $value as $i => $single ) {
-				if ( !in_array( $single, $possible_values ) ) {
-					unset( $value[$i] );
-				}
-			}
+	// For certain fields, only certain parameters are acceptable, so add them to the whitelist.
+	if ( $field_type_obj->supports_options ) {
+		$field_type_obj->set_whitelist_values( wp_list_pluck( $field->get_children(), 'name' ) );
+	}
 
-			// Reset the keys by merging with an empty array
-			$value = array_merge( array(), $value );
-		} else {
-			if ( !in_array( $value, $possible_values ) ) {
-				return false;
-			}
-		}
+	// Check the value is in an accepted format for this form field.
+	if ( ! $field_type_obj->is_valid( $value ) ) {
+		return false;
 	}
 
 	$field           = new BP_XProfile_ProfileData();
diff --git a/bp-xprofile/bp-xprofile-loader.php b/bp-xprofile/bp-xprofile-loader.php
index 922877c..3477fe1 100644
--- a/bp-xprofile/bp-xprofile-loader.php
+++ b/bp-xprofile/bp-xprofile-loader.php
@@ -98,15 +98,7 @@ class BP_XProfile_Component extends BP_Component {
 		}
 
 		// Set the support field type ids
-		$this->field_types = apply_filters( 'xprofile_field_types', array(
-			'textbox',
-			'textarea',
-			'radio',
-			'checkbox',
-			'selectbox',
-			'multiselectbox',
-			'datebox'
-		) );
+		$this->field_types = apply_filters( 'xprofile_field_types', array_keys( bp_xprofile_get_field_types() ) );
 
 		// Register the visibility levels. See bp_xprofile_get_visibility_levels() to filter
 		$this->visibility_levels = array(
diff --git a/bp-xprofile/bp-xprofile-template.php b/bp-xprofile/bp-xprofile-template.php
index ff320d9..aa87a64 100644
--- a/bp-xprofile/bp-xprofile-template.php
+++ b/bp-xprofile/bp-xprofile-template.php
@@ -221,6 +221,7 @@ function bp_field_css_class( $class = false ) {
 		if ( $profile_template->current_field % 2 == 1 )
 			$css_classes[] = 'alt';
 
+		$css_classes[] = 'field_type_' . sanitize_title( $profile_template->field->type );
 		$css_classes = apply_filters_ref_array( 'bp_field_css_classes', array( &$css_classes ) );
 
 		return apply_filters( 'bp_get_field_css_class', ' class="' . implode( ' ', $css_classes ) . '"' );
@@ -386,11 +387,7 @@ function bp_the_profile_field_input_name() {
 	function bp_get_the_profile_field_input_name() {
 		global $field;
 
-		$array_box = false;
-		if ( 'multiselectbox' == $field->type )
-			$array_box = '[]';
-
-		return apply_filters( 'bp_get_the_profile_field_input_name', 'field_' . $field->id . $array_box );
+		return apply_filters( 'bp_get_the_profile_field_input_name', 'field_' . $field->id );
 	}
 
 /**
@@ -423,14 +420,13 @@ function bp_get_the_profile_field_errors_action() {
  *
  * @param array $args Specify type for datebox. Allowed 'day', 'month', 'year'.
  */
-function bp_the_profile_field_options( $args = '' ) {
+function bp_the_profile_field_options( $args = array() ) {
 	echo bp_get_the_profile_field_options( $args );
 }
 	/**
 	 * bp_get_the_profile_field_options()
 	 *
-	 * Retrieves field options HTML for field types of 'selectbox', 'multiselectbox',
-	 * 'radio', 'checkbox', and 'datebox'.
+	 * Retrieves field options HTML for field types of 'selectbox', 'multiselectbox', 'radio', 'checkbox', and 'datebox'.
 	 *
 	 * @package BuddyPress Xprofile
 	 * @since BuddyPress (1.1)
@@ -446,277 +442,33 @@ function bp_the_profile_field_options( $args = '' ) {
 	 *           used when rendering options. Default: displayed user.
 	 * }
 	 */
-	function bp_get_the_profile_field_options( $args = '' ) {
+	function bp_get_the_profile_field_options( $args = array() ) {
 		global $field;
 
-		$defaults = array(
+		$args = bp_parse_args( $args, array(
 			'type'    => false,
 			'user_id' => bp_displayed_user_id(),
-		);
-
-		$r = wp_parse_args( $args, $defaults );
-		extract( $r, EXTR_SKIP );
+		), 'get_the_profile_field_options' );
 
-		// In some cases, the $field global is not an instantiation of the BP_XProfile_Field
-		// class. However, we have to make sure that all data originally in $field gets
-		// merged back in, after reinstantiation.
-		if ( !method_exists( $field, 'get_children' ) ) {
+		/**
+		 * In some cases, the $field global is not an instantiation of the BP_XProfile_Field class.
+		 * However, we have to make sure that all data originally in $field gets merged back in, after reinstantiation.
+		 */
+		if ( ! method_exists( $field, 'get_children' ) ) {
 			$field_obj = new BP_XProfile_Field( $field->id );
 
-			foreach( $field as $field_prop => $field_prop_value ) {
-				if ( !isset( $field_obj->{$field_prop} ) ) {
+			foreach ( $field as $field_prop => $field_prop_value ) {
+				if ( ! isset( $field_obj->{$field_prop} ) )
 					$field_obj->{$field_prop} = $field_prop_value;
-				}
 			}
 
 			$field = $field_obj;
 		}
 
-		$options = $field->get_children();
-
-		// Setup some defaults
-		$html     = '';
-		$selected = '';
-
-		switch ( $field->type ) {
-			case 'selectbox':
-
-				$html .= '<option value="">' . /* translators: no option picked in select box */ __( '----', 'buddypress' ) . '</option>';
-
-				$original_option_values = '';
-				$original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id ) );
-
-				if ( empty( $original_option_values ) && !empty( $_POST['field_' . $field->id] ) ) {
-					$original_option_values = $_POST['field_' . $field->id];
-				}
-
-				$option_values = (array) $original_option_values;
-
-				for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
-
-					// Check for updated posted values, but errors preventing them from being saved first time
-					foreach( $option_values as $i => $option_value ) {
-						if ( isset( $_POST['field_' . $field->id] ) && $_POST['field_' . $field->id] != $option_value ) {
-							if ( !empty( $_POST['field_' . $field->id] ) ) {
-								$option_values[$i] = $_POST['field_' . $field->id];
-							}
-						}
-					}
-
-					$selected = '';
-
-					// Run the allowed option name through the before_save filter, so we'll be sure to get a match
-					$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
-
-					// First, check to see whether the user-entered value matches
-					if ( in_array( $allowed_options, (array) $option_values ) ) {
-						$selected = ' selected="selected"';
-					}
-
-					// Then, if the user has not provided a value, check for defaults
-					if ( !is_array( $original_option_values ) && empty( $option_values ) && $options[$k]->is_default_option ) {
-						$selected = ' selected="selected"';
-					}
-
-					$html .= apply_filters( 'bp_get_the_profile_field_options_select', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_attr( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $field->id, $selected, $k );
-				}
-				break;
-
-			case 'multiselectbox':
-				$original_option_values = '';
-				$original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id ) );
-
-				if ( empty( $original_option_values ) && !empty( $_POST['field_' . $field->id] ) ) {
-					$original_option_values = $_POST['field_' . $field->id];
-				}
-
-				$option_values = (array) $original_option_values;
-
-				for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
-
-					// Check for updated posted values, but errors preventing them from being saved first time
-					foreach( $option_values as $i => $option_value ) {
-						if ( isset( $_POST['field_' . $field->id] ) && $_POST['field_' . $field->id][$i] != $option_value ) {
-							if ( !empty( $_POST['field_' . $field->id][$i] ) ) {
-								$option_values[] = $_POST['field_' . $field->id][$i];
-							}
-						}
-					}
-					$selected = '';
-
-					// Run the allowed option name through the before_save filter, so we'll be sure to get a match
-					$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
-
-					// First, check to see whether the user-entered value matches
-					if ( in_array( $allowed_options, (array) $option_values ) ) {
-						$selected = ' selected="selected"';
-					}
-
-					// Then, if the user has not provided a value, check for defaults
-					if ( !is_array( $original_option_values ) && empty( $option_values ) && !empty( $options[$k]->is_default_option ) ) {
-						$selected = ' selected="selected"';
-					}
-
-					$html .= apply_filters( 'bp_get_the_profile_field_options_multiselect', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_attr( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $field->id, $selected, $k );
-				}
-				break;
-
-			case 'radio':
-				$html .= '<div id="field_' . $field->id . '">';
-				$option_value = BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id );
-
-				for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
-
-					// Check for updated posted values, but errors preventing them from being saved first time
-					if ( isset( $_POST['field_' . $field->id] ) && $option_value != $_POST['field_' . $field->id] ) {
-						if ( !empty( $_POST['field_' . $field->id] ) ) {
-							$option_value = $_POST['field_' . $field->id];
-						}
-					}
-
-					// Run the allowed option name through the before_save
-					// filter, so we'll be sure to get a match
-					$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
-					$selected        = '';
-
-					if ( $option_value == $allowed_options || ( empty( $option_value ) && !empty( $options[$k]->is_default_option ) ) )
-						$selected = ' checked="checked"';
-
-					$html .= apply_filters( 'bp_get_the_profile_field_options_radio', '<label><input' . $selected . ' type="radio" name="field_' . $field->id . '" id="option_' . $options[$k]->id . '" value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '"> ' . esc_attr( stripslashes( $options[$k]->name ) ) . '</label>', $options[$k], $field->id, $selected, $k );
-				}
-
-				$html .= '</div>';
-				break;
-
-			case 'checkbox':
-				$option_values = BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id );
-				$option_values = (array) maybe_unserialize( $option_values );
-
-				// Check for updated posted values, but errors preventing them from being saved first time
-				if ( isset( $_POST['field_' . $field->id] ) && $option_values != maybe_serialize( $_POST['field_' . $field->id] ) ) {
-					if ( !empty( $_POST['field_' . $field->id] ) )
-						$option_values = $_POST['field_' . $field->id];
-				}
-
-				for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
-					$selected = '';
-
-					// First, check to see whether the user's saved values
-					// match the option
-					for ( $j = 0, $count_values = count( $option_values ); $j < $count_values; ++$j ) {
-
-						// Run the allowed option name through the
-						// before_save filter, so we'll be sure to get a match
-						$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
-
-						if ( $option_values[$j] == $allowed_options || @in_array( $allowed_options, $option_values ) ) {
-							$selected = ' checked="checked"';
-							break;
-						}
-					}
-
-					// If the user has not yet supplied a value for this field,
-					// check to see whether there is a default value available
-					if ( !is_array( $option_values ) && empty( $option_values ) && empty( $selected ) && !empty( $options[$k]->is_default_option ) ) {
-						$selected = ' checked="checked"';
-					}
-
-					$html .= apply_filters( 'bp_get_the_profile_field_options_checkbox', '<label><input' . $selected . ' type="checkbox" name="field_' . $field->id . '[]" id="field_' . $options[$k]->id . '_' . $k . '" value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '"> ' . esc_attr( stripslashes( $options[$k]->name ) ) . '</label>', $options[$k], $field->id, $selected, $k );
-				}
-				break;
-
-			case 'datebox':
-				$date = BP_XProfile_ProfileData::get_value_byid( $field->id, $user_id );
-
-				// Set day, month, year defaults
-				$day   = '';
-				$month = '';
-				$year  = '';
-
-				if ( !empty( $date ) ) {
-
-					// If Unix timestamp
-					if ( is_numeric( $date ) ) {
-						$day   = date( 'j', $date );
-						$month = date( 'F', $date );
-						$year  = date( 'Y', $date );
-
-					// If MySQL timestamp
-					} else {
-						$day   = mysql2date( 'j', $date );
-						$month = mysql2date( 'F', $date, false ); // Not localized, so that selected() works below
-						$year  = mysql2date( 'Y', $date );
-					}
-				}
-
-				// Check for updated posted values, and errors preventing
-				// them from being saved first time.
-				if ( !empty( $_POST['field_' . $field->id . '_day'] ) ) {
-					if ( $day != $_POST['field_' . $field->id . '_day'] ) {
-						$day = $_POST['field_' . $field->id . '_day'];
-					}
-				}
-
-				if ( !empty( $_POST['field_' . $field->id . '_month'] ) ) {
-					if ( $month != $_POST['field_' . $field->id . '_month'] ) {
-						$month = $_POST['field_' . $field->id . '_month'];
-					}
-				}
-
-				if ( !empty( $_POST['field_' . $field->id . '_year'] ) ) {
-					if ( $year != date( "j", $_POST['field_' . $field->id . '_year'] ) ) {
-						$year = $_POST['field_' . $field->id . '_year'];
-					}
-				}
-
-				// $type will be passed by calling function when needed
-				switch ( $type ) {
-					case 'day':
-						$html .= '<option value=""' . selected( $day, '', false ) . '>--</option>';
-
-						for ( $i = 1; $i < 32; ++$i ) {
-							$html .= '<option value="' . $i .'"' . selected( $day, $i, false ) . '>' . $i . '</option>';
-						}
-						break;
-
-					case 'month':
-						$eng_months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' );
-
-						$months = array(
-							__( 'January', 'buddypress' ),
-							__( 'February', 'buddypress' ),
-							__( 'March', 'buddypress' ),
-							__( 'April', 'buddypress' ),
-							__( 'May', 'buddypress' ),
-							__( 'June', 'buddypress' ),
-							__( 'July', 'buddypress' ),
-							__( 'August', 'buddypress' ),
-							__( 'September', 'buddypress' ),
-							__( 'October', 'buddypress' ),
-							__( 'November', 'buddypress' ),
-							__( 'December', 'buddypress' )
-						);
-
-						$html .= '<option value=""' . selected( $month, '', false ) . '>------</option>';
-
-						for ( $i = 0; $i < 12; ++$i ) {
-							$html .= '<option value="' . $eng_months[$i] . '"' . selected( $month, $eng_months[$i], false ) . '>' . $months[$i] . '</option>';
-						}
-						break;
-
-					case 'year':
-						$html .= '<option value=""' . selected( $year, '', false ) . '>----</option>';
-
-						for ( $i = 2037; $i > 1901; $i-- ) {
-							$html .= '<option value="' . $i .'"' . selected( $year, $i, false ) . '>' . $i . '</option>';
-						}
-						break;
-				}
-
-				$html = apply_filters( 'bp_get_the_profile_field_datebox', $html, $type, $day, $month, $year, $field->id, $date );
-
-				break;
-		}
+		ob_start();
+		$field->type_obj->edit_field_options_html( $args );
+		$html = ob_get_contents();
+		ob_end_clean();
 
 		return $html;
 	}
@@ -1014,4 +766,4 @@ function bp_xprofile_settings_visibility_select( $args = '' ) {
 
 		// Output the dropdown list
 		return apply_filters( 'bp_xprofile_settings_visibility_select', ob_get_clean() );
-	}
\ No newline at end of file
+	}
diff --git a/tests/testcases/xprofile/class-bp-xprofile-field-type.php b/tests/testcases/xprofile/class-bp-xprofile-field-type.php
new file mode 100644
index 0000000..4511262
--- /dev/null
+++ b/tests/testcases/xprofile/class-bp-xprofile-field-type.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * @group xprofile
+ * @group BP_XProfile_Field_Type
+ */
+class BP_Tests_XProfile_Field_Type extends BP_UnitTestCase {
+	public function setUp() {
+		parent::setUp();
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+	}
+
+	public function test_unregistered_field_type_returns_textbox() {
+		$field = bp_xprofile_create_field_type( 'fakeyfield' );
+		$this->assertEquals( get_class( $field ), 'BP_XProfile_Field_Type_Textbox' );
+	}
+
+
+	public function test_textbox_validate_empty_string() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+		$this->assertTrue( $field->is_valid( '' ) );
+	}
+
+	public function test_textbox_validate_string() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+		$this->assertTrue( $field->is_valid( 'my 117th input string!' ) );
+	}
+
+	public function test_textbox_validate_integer() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+		$this->assertTrue( $field->is_valid( 123 ) );
+	}
+
+	public function test_textbox_validate_whitelisted_string() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+
+		$this->assertTrue( $field->is_valid( 'a string' ) );
+		$this->assertFalse( $field->set_whitelist_values( 'pizza' )->is_valid( 'pasta' ) );
+		$this->assertTrue( $field->is_valid( 'pizza' ) );
+	}
+
+
+	public function test_multiselectbox_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'multiselectbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertTrue( $field->is_valid( array( 'cheese', 'pepporoni' ) ) );
+		$this->assertTrue( $field->is_valid( array( 'cheese' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'cheese', 'pepporoni', 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'pinapple' ) ) );
+	}
+
+	public function test_multiselectbox_validate_null_value() {
+		$field = bp_xprofile_create_field_type( 'multiselectbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertFalse( $field->is_valid( array( '' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+		$this->assertTrue( $field->is_valid( array() ) );
+	}
+
+
+	public function test_datebox_do_not_validate_string() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertFalse( $field->is_valid( 'datebox fields only accept strings in: Y-m-d 00:00:00' ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_datebox_do_not_validate_integer() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertFalse( $field->is_valid( 221213 ) );
+	}
+
+	public function test_datebox_validate_date() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertTrue( $field->is_valid( '2013-12-22 00:00:00' ) );
+	}
+
+	public function test_datebox_do_not_validate_date_with_timestamp() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertFalse( $field->is_valid( '2013-12-22 19:11:30' ) );
+		$this->assertFalse( $field->is_valid( '2013-12-22' ) );
+	}
+
+
+	public function test_number_do_not_validate_string() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$this->assertFalse( $field->is_valid( 'telephone fields only accept integers' ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+ 	public function test_number_validate_integer() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$this->assertTrue( $field->is_valid( 12345678901 ) );
+	}
+
+
+	public function test_radiobutton_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'radio' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertTrue( $field->is_valid( array( 'cheese', 'pepporoni' ) ) );
+		$this->assertTrue( $field->is_valid( array( 'cheese' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'cheese', 'pepporoni', 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_radiobutton_do_not_validate_empty_items_in_whitelist() {
+		$field = bp_xprofile_create_field_type( 'radio' );
+		$field->set_whitelist_values( array( '' ) );
+
+		$this->assertFalse( $field->is_valid( array( '' ) ) );
+	}
+
+
+	public function test_checkbox_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'checkbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertTrue( $field->is_valid( array( 'cheese', 'pepporoni' ) ) );
+		$this->assertTrue( $field->is_valid( array( 'cheese' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'cheese', 'pepporoni', 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_checkbox_validate_null_value() {
+		$field = bp_xprofile_create_field_type( 'checkbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertFalse( $field->is_valid( array( '' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+		$this->assertTrue( $field->is_valid( array() ) );
+	}
+}
