diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 8d99b12102d5595475581c15a39fe2c365382500..1eee2c654003c7ca520c8b6ab6ec673f45d8d12e 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -136,12 +136,12 @@
           break;
         case 'groups:group_members:index':
           new gl.MemberExpirationDate();
-          new GroupMembers();
+          new gl.Members();
           new UsersSelect();
           break;
         case 'projects:project_members:index':
           new gl.MemberExpirationDate();
-          new ProjectMembers();
+          new gl.Members();
           new UsersSelect();
           break;
         case 'groups:new':
diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js
deleted file mode 100644
index 4382dd6860f542d75fdb69d94adca8e82397ed04..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/groups.js
+++ /dev/null
@@ -1,13 +0,0 @@
-(function() {
-  this.GroupMembers = (function() {
-    function GroupMembers() {
-      $('li.group_member').bind('ajax:success', function() {
-        return $(this).fadeOut();
-      });
-    }
-
-    return GroupMembers;
-
-  })();
-
-}).call(this);
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 1935af491f713d3c3d613471285b4068eb11cd8d..e1532fd9ec426fe86b87fc2b5a92d24e27b5109b 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -14,14 +14,18 @@
     inputs.datepicker({
       dateFormat: 'yy-mm-dd',
       minDate: 1,
-      onSelect: toggleClearInput
+      onSelect: function () {
+        $(this).trigger('change');
+        toggleClearInput.call(this);
+      }
     });
 
     inputs.next('.js-clear-input').on('click', function(event) {
       event.preventDefault();
 
       var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
-      input.datepicker('setDate', null);
+      input.datepicker('setDate', null)
+        .trigger('change');
       toggleClearInput.call(input);
     });
 
diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..a0cd20f21e801535dad610c58e5ed514156ea6d9
--- /dev/null
+++ b/app/assets/javascripts/members.js.es6
@@ -0,0 +1,36 @@
+((w) => {
+  w.gl = w.gl || {};
+
+  class Members {
+    constructor() {
+      this.addListeners();
+    }
+
+    addListeners() {
+      $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
+      $('.js-member-update-control').off('change').on('change', this.formSubmit);
+      $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
+    }
+
+    removeRow(e) {
+      const $target = $(e.target);
+
+      if ($target.hasClass('btn-remove')) {
+        $target.closest('.member')
+          .fadeOut(function () {
+            $(this).remove();
+          });
+      }
+    }
+
+    formSubmit() {
+      $(this).closest('form').trigger("submit.rails").end().disable();
+    }
+
+    formSuccess() {
+      $(this).find('.js-member-update-control').enable();
+    }
+  }
+
+  gl.Members = Members;
+})(window);
diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js
deleted file mode 100644
index 78f7b48bc7d726d043ea51a09c27bd04c79b17ba..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_members.js
+++ /dev/null
@@ -1,10 +0,0 @@
-(function() {
-  this.ProjectMembers = (function() {
-    function ProjectMembers() {
-      $('li.project_member').bind('ajax:success', function() {
-        return $(this).fadeOut();
-      });
-    }
-    return ProjectMembers;
-  })();
-}).call(this);
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 37ff7e22ed1e3c833ce6fbd5410fd5f8583c405a..d1f1a372c06b25c78ccf73e4a21ec303b45dd781 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -125,7 +125,3 @@ label {
     border-right: 0;
   }
 }
-
-.help-block {
-  margin-bottom: 0;
-}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index efc348214c264d5b89601111eb91e728063e2f81..9114425cfdd431a4f6931d80002c2e0548c0da2f 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -128,6 +128,10 @@ ul.content-list {
       color: $gl-dark-link-color;
     }
 
+    .member-group-link {
+      color: $blue-normal;
+    }
+
     .description {
       p {
         @include str-truncated;
@@ -168,6 +172,14 @@ ul.content-list {
       }
     }
 
+    .member-controls {
+      float: none;
+
+      @media (min-width: $screen-sm-min) {
+        float: right;
+      }
+    }
+
     // When dragging a list item
     &.ui-sortable-helper {
       border-bottom: none;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index c6f30e144fdcedfe82350df44c72cda2b6404b86..5ba0486177fa49bfbbe3053cf2e4cc748ddf692e 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -13,6 +13,11 @@
     .dropdown-menu-toggle {
       line-height: 20px;
     }
+
+    .badge {
+      margin-top: -2px;
+      margin-left: 5px;
+    }
   }
 
   .panel-body {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index c75dacf95d9a80e856c4523a3d55950de1f1be9e..58f9db0fb21cebef150e872c06b82431ae2610cf 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -86,7 +86,7 @@
     background: none;
 
     .select2-search-field input {
-      padding: $gl-padding / 2;
+      padding: 5px $gl-padding / 2;
       font-size: 13px;
       height: auto;
       font-family: inherit;
@@ -94,7 +94,7 @@
     }
 
     .select2-search-choice {
-      margin: 8px 0 0 8px;
+      margin: 5px 0 0 8px;
       box-shadow: none;
       border-color: $input-border;
       color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 185ce970e719fe72086185cd2c8ceeca52e7de76..edc9592f5640e0d7478894ba637f07c856d002ba 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,17 +1,3 @@
-.member-search-form {
-  float: left;
-
-  input[type='search'] {
-    width: 225px;
-    vertical-align: bottom;
-
-    @media (max-width: $screen-xs-max) {
-      width: 100px;
-      vertical-align: bottom;
-    }
-  }
-}
-
 .milestone-row {
   @include str-truncated(90%);
 }
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
new file mode 100644
index 0000000000000000000000000000000000000000..756efa9c7fa027433b0643a602b32f02e10c0166
--- /dev/null
+++ b/app/assets/stylesheets/pages/members.scss
@@ -0,0 +1,98 @@
+.project-members-title {
+  padding-bottom: 10px;
+  border-bottom: 1px solid $border-color;
+}
+
+.member {
+  .list-item-name {
+    @media (min-width: $screen-sm-min) {
+      float: left;
+      width: 50%;
+    }
+
+    strong {
+      font-weight: 600;
+    }
+  }
+
+  .controls {
+    @media (min-width: $screen-sm-min) {
+      display: -webkit-flex;
+      display: flex;
+      width: 400px;
+      max-width: 50%;
+    }
+  }
+
+  .form-horizontal {
+    margin-top: 5px;
+
+    @media (min-width: $screen-sm-min) {
+      display: -webkit-flex;
+      display: flex;
+      width: 100%;
+      margin-top: 3px;
+    }
+  }
+
+  .btn-remove {
+    width: 100%;
+
+    @media (min-width: $screen-sm-min) {
+      width: auto;
+    }
+  }
+}
+
+.member-form-control {
+  @media (max-width: $screen-xs-max) {
+    padding: 5px 0;
+    margin-left: 0;
+    margin-right: 0;
+  }
+
+  @media (min-width: $screen-sm-min) {
+    width: 50%;
+  }
+}
+
+.member-access-text {
+  margin-left: auto;
+  line-height: 43px;
+}
+
+.member.existing-title {
+  @media (min-width: $screen-sm-min) {
+    float: left;
+  }
+}
+
+.member-search-form {
+  position: relative;
+
+  @media (min-width: $screen-sm-min) {
+    float: right;
+  }
+
+  .form-control {
+    width: 100%;
+    padding-right: 35px;
+
+    @media (min-width: $screen-sm-min) {
+      width: 350px;
+    }
+  }
+}
+
+.member-search-btn {
+  position: absolute;
+  right: 0;
+  top: 0;
+  height: 35px;
+  padding-left: 10px;
+  padding-right: 10px;
+  color: $gray-darkest;
+  background: transparent;
+  border: 0;
+  outline: 0;
+}
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 7a7475a734503ce50763314fa354498fbfc4e013..ae060abee5c6af949f4a70c9d919280130ca47d8 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -1,6 +1,7 @@
 class Projects::GroupLinksController < Projects::ApplicationController
   layout 'project_settings'
   before_action :authorize_admin_project!
+  before_action :authorize_admin_project_member!, only: [:update]
 
   def index
     @group_links = project.project_group_links.all
@@ -27,9 +28,26 @@ class Projects::GroupLinksController < Projects::ApplicationController
     redirect_to namespace_project_group_links_path(project.namespace, project)
   end
 
+  def update
+    @group_link = @project.project_group_links.find(params[:id])
+
+    @group_link.update_attributes(group_link_params)
+  end
+
   def destroy
     project.project_group_links.find(params[:id]).destroy
 
-    redirect_to namespace_project_group_links_path(project.namespace, project)
+    respond_to do |format|
+      format.html do
+        redirect_to namespace_project_group_links_path(project.namespace, project)
+      end
+      format.js { head :ok }
+    end
+  end
+
+  protected
+
+  def group_link_params
+    params.require(:group_link).permit(:group_access, :expires_at)
   end
 end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index f56b256984ba27347a49ab099c28edf5cd3080e5..37a86ed0523e422df930d41e42c5ac18ae264f94 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -5,34 +5,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController
   before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
 
   def index
+    @group_links = @project.project_group_links
+
     @project_members = @project.project_members
     @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
 
     if params[:search].present?
       users = @project.users.search(params[:search]).to_a
       @project_members = @project_members.where(user_id: users)
-    end
-
-    @project_members = @project_members.order('access_level DESC')
-
-    @group = @project.group
-
-    if @group
-      @group_members = @group.group_members
-      @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
-
-      if params[:search].present?
-        users = @group.users.search(params[:search]).to_a
-        @group_members = @group_members.where(user_id: users)
-      end
 
-      @group_members = @group_members.order('access_level DESC')
+      @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
     end
 
+    @project_members = @project_members.order(access_level: :desc).page(params[:page])
+
     @requesters = AccessRequestsFinder.new(@project).execute(current_user)
 
     @project_member = @project.project_members.new
-    @project_group_links = @project.project_group_links
   end
 
   def create
@@ -43,6 +32,21 @@ class Projects::ProjectMembersController < Projects::ApplicationController
       current_user: current_user
     )
 
+    if params[:group_ids].present?
+      group_ids = params[:group_ids].split(',')
+      groups = Group.where(id: group_ids)
+
+      groups.each do |group|
+        next unless can?(current_user, :read_group, group)
+
+        project.project_group_links.create(
+          group: group,
+          group_access: params[:access_level],
+          expires_at: params[:expires_at]
+        )
+      end
+    end
+
     redirect_to namespace_project_project_members_path(@project.namespace, @project)
   end
 
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index 2fb3190ab11e304162f349f77feb95c75caaafc7..b185b81db7ff5d2cee606034cdb82e0762520fd2 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -1,27 +1,22 @@
-= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
-  .form-group
-    = f.label :user_ids, "People", class: 'control-label'
-    .col-sm-10
-      = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
-      .help-block
+= form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f|
+  .row
+    .col-md-4.col-lg-6
+      = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true)
+      .help-block.append-bottom-10
         Search for users by name, username, or email, or invite new ones using their email address.
 
-  .form-group
-    = f.label :access_level, "Group Access", class: 'control-label'
-    .col-sm-10
-      = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2"
-      .help-block
-        Read more about role permissions
-        %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
+    .col-md-3.col-lg-2
+      = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select"
+      .help-block.append-bottom-10
+        = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+        about role permissions
 
-  .form-group
-    = f.label :expires_at, 'Access expiration date', class: 'control-label'
-    .col-sm-10
+    .col-md-3.col-lg-2
       .clearable-input
-        = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
+        = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
         %i.clear-icon.js-clear-input
-      .help-block
+      .help-block.append-bottom-10
         On this date, the user(s) will automatically lose access to this group and all of its projects.
 
-  .form-actions
-    = f.submit 'Add users to group', class: "btn btn-create"
+    .col-md-2
+      = f.submit 'Add to group', class: "btn btn-create btn-block"
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index f789796e9429f006e248983bfcb12145f59f116f..ebf9aca7700849b90e958f0a851fa37c9f6fcafc 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,35 +1,31 @@
 - page_title "Members"
 
-.group-members-page.prepend-top-default
+.project-members-page.prepend-top-default
+  %h4
+    Members
+  %hr
   - if can?(current_user, :admin_group_member, @group)
-    .panel.panel-default
-      .panel-heading
-        Add new user to group
-      .panel-body
-        %p.light
-          Members of group have access to all group projects.
-        .new-group-member-holder
-          = render "new_group_member"
+    .project-members-new.append-bottom-default
+      %p.clearfix
+        Add new user to
+        %strong= @group.name
+      = render "new_group_member"
 
     = render 'shared/members/requests', membership_source: @group, requesters: @requesters
 
+  .append-bottom-default.clearfix
+    %h5.member.existing-title
+      Existing users
+    = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form'  do
+      .form-group
+        = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
+        %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
+          = icon("search")
   .panel.panel-default
     .panel-heading
+      Users with access to
       %strong #{@group.name}
-      group members
       %span.badge= @members.total_count
-      .controls
-        = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form'  do
-          .form-group
-            = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
-          = button_tag class: 'btn', title: 'Search' do
-            = icon("search")
     %ul.content-list
       = render partial: 'shared/members/member', collection: @members, as: :member
     = paginate @members, theme: 'gitlab'
-
-:javascript
-  $('form.member-search-form').on('submit', function(event) {
-    event.preventDefault();
-    Turbolinks.visit(this.action + '?' + $(this).serialize());
-  });
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 3be7ed8432ce18d7a276dbf134a451a1b7d8867a..de8f53b6b52ba27088c628c6970303ace0a6fbc7 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,3 +1,3 @@
 :plain
-  $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
-  new gl.MemberExpirationDate();
+  var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}');
+  $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml
new file mode 100644
index 0000000000000000000000000000000000000000..af9a5b190600c0bf5f4fb93c0260d4456003d5ea
--- /dev/null
+++ b/app/views/projects/group_links/update.js.haml
@@ -0,0 +1,3 @@
+:plain
+  var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}');
+  $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name'));
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index e783d8c72c52dd82190f1b21c48844555e7910fb..9738f369a35368d50cbb842893b7b4698066df3b 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -1,7 +1,7 @@
 .panel.panel-default
   .panel-heading
+    Group members with access to
     %strong #{@group.name}
-    group members
     %span.badge= members.size
     - if can?(current_user, :admin_group_member, @group)
       .controls
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..d7f5fa965270246888f60fa0ce1c5590e05df7ef
--- /dev/null
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -0,0 +1,7 @@
+.panel.panel-default.project-members-groups
+  .panel-heading
+    Groups with access to
+    %strong #{@project.name}
+    %span.badge= group_links.size
+  %ul.content-list
+    = render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index fa8cbf717337b1c63a79f5ffc3ead340aff05e6c..79dcd7a6ee9acb501ef63897603233be50dcbd0d 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,27 +1,22 @@
-= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
-  .form-group
-    = f.label :user_ids, "People", class: 'control-label'
-    .col-sm-10
-      = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
-      .help-block
+= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
+  .row
+    .col-md-4.col-lg-6
+      = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true)
+      .help-block.append-bottom-10
         Search for users by name, username, or email, or invite new ones using their email address.
 
-  .form-group
-    = f.label :access_level, "Project Access", class: 'control-label'
-    .col-sm-10
-      = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2"
-      .help-block
-        Read more about role permissions
-        %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
+    .col-md-3.col-lg-2
+      = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
+      .help-block.append-bottom-10
+        = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+        about role permissions
 
-  .form-group
-    = f.label :expires_at, 'Access expiration date', class: 'control-label'
-    .col-sm-10
+    .col-md-3.col-lg-2
       .clearable-input
-        = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
+        = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
         %i.clear-icon.js-clear-input
-      .help-block
+      .help-block.append-bottom-10
         On this date, the user(s) will automatically lose access to this project.
 
-  .form-actions
-    = f.submit 'Add users to project', class: "btn btn-create"
+    .col-md-2
+      = f.submit "Add to project", class: "btn btn-create btn-block"
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index b0bfdd235f7b24277909ee78f76705387fe8fe96..c1e894d8f40f1395c5c2dc46800634a21a4c44d9 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,19 +1,7 @@
 .panel.panel-default
   .panel-heading
+    Users with access to
     %strong #{@project.name}
-    project members
-    %span.badge= members.size
-    .controls
-      = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form'  do
-        .form-group
-          = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
-        = button_tag class: 'btn', title: 'Search' do
-          = icon("search")
+    %span.badge= @project_members.total_count
   %ul.content-list
     = render partial: 'shared/members/member', collection: members, as: :member
-
-:javascript
-  $('form.member-search-form').on('submit', function (event) {
-    event.preventDefault();
-    Turbolinks.visit(this.action + '?' + $(this).serialize());
-  });
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9d063b3081f878a40c176954ac8ac18ce51cb892..bdeb704b6daa6a7c01393f5aab0f5778a168c185 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,24 +1,28 @@
 - page_title "Members"
 
-.project-members-page.js-project-members-page.prepend-top-default
+.project-members-page.prepend-top-default
+  %h4.project-members-title.clearfix
+    Members
+    = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project"
   - if can?(current_user, :admin_project_member, @project)
-    .panel.panel-default
-      .panel-heading
-        Add new user to project
-        .controls
-          = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
-            Import members
-      .panel-body
-        %p.light
-          Users with access to this project are listed below.
-        = render "new_project_member"
+    .project-members-new.append-bottom-default
+      %p.clearfix
+        Add new user to
+        %strong= @project.name
+      = render "new_project_member"
 
-    = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+      = render 'shared/members/requests', membership_source: @project, requesters: @requesters
 
-  = render 'team', members: @project_members
-
-  - if @group
-    = render "group_members", members: @group_members
+  .append-bottom-default.clearfix
+    %h5.member.existing-title
+      Existing users and groups
+    = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form'  do
+      .form-group
+        = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
+        %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
+          = icon("search")
+  - if @group_links.any?
+    = render 'groups', group_links: @group_links
 
-  - if @project_group_links.any? && @project.allowed_to_share_with_group?
-    = render "shared_group_members"
+  = render 'team', members: @project_members
+  = paginate @project_members, theme: "gitlab"
diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml
index 37e55dc72a31b6457a87dbc207a0c02cd7e63974..91927181efbeba7fd4145f1a203189c5859e34a6 100644
--- a/app/views/projects/project_members/update.js.haml
+++ b/app/views/projects/project_members/update.js.haml
@@ -1,3 +1,3 @@
 :plain
-  $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}');
-  new gl.MemberExpirationDate();
+  var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}');
+  $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..1c0346bbc78f9269a1625ca540237e7f0937f438
--- /dev/null
+++ b/app/views/shared/members/_group.html.haml
@@ -0,0 +1,29 @@
+- group_link = local_assigns[:group_link]
+- group = group_link.group
+- can_admin_member = can?(current_user, :admin_project_member, @project)
+%li.member.group_member{ id: "group_member_#{group_link.id}" }
+  %span{ class: "list-item-name" }
+    = image_tag group_icon(group), class: "avatar s40", alt: ''
+    %strong
+      = link_to group.name, group_path(group)
+    .cgray
+      Joined #{time_ago_with_tooltip(group.created_at)}
+      - if group_link.expires?
+        ·
+        %span{ class: ('text-warning' if group_link.expires_soon?) }
+          Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
+  .controls.member-controls
+    = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
+      = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member
+      .prepend-left-5.clearable-input.member-form-control
+        = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
+        %i.clear-icon.js-clear-input
+    - if can_admin_member
+      = link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
+        remote: true,
+        method: :delete,
+        data: { confirm: "Are you sure you want to remove #{group.name}?" },
+        class: 'btn btn-remove prepend-left-10' do
+        %span.visible-xs-block
+          Delete
+        = icon('trash', class: 'hidden-xs')
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 5f20e4bd42af1dc32ec28128e48f311c250e97fc..432047a1c4ed6dd94fbfd947da4b3a2f79c7a0ba 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,59 +1,29 @@
 - show_roles = local_assigns.fetch(:show_roles, true)
 - show_controls = local_assigns.fetch(:show_controls, true)
-- user = member.user
+- user = local_assigns.fetch(:user, member.user)
+- source = member.source
+- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
 
-%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) }
-  - if show_roles
-    .controls
-      %strong.control-text= member.human_access
-      - if show_controls
-        - if !user && can?(current_user, action_member_permission(:admin, member), member.source)
-          = link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
-                    method: :post,
-                    class: 'btn'
-
-        - if can?(current_user, action_member_permission(:update, member), member)
-          = button_tag icon('pencil'),
-                       type: 'button',
-                       class: 'btn inline js-toggle-button',
-                       title: 'Edit'
-
-          - if member.request?
-            = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
-                      method: :post,
-                      class: 'btn btn-success',
-                      title: 'Grant access'
-
-        - if can?(current_user, action_member_permission(:destroy, member), member)
-          - if current_user == user
-            = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
-                      method: :delete,
-                      data: { confirm: leave_confirmation_message(member.source) },
-                      class: 'btn btn-remove'
-          - else
-            = link_to icon('trash'), member,
-                      remote: true,
-                      method: :delete,
-                      data: { confirm: remove_member_message(member) },
-                      class: 'btn btn-remove',
-                      title: remove_member_title(member)
-
-
-  %span{ class: ("list-item-name" if show_controls) }
+%li.member{ class: dom_class(member), id: dom_id(member) }
+  %span.list-item-name
     - if user
       = image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
       %strong
         = link_to user.name, user_path(user)
-      %span.cgray= user.username
+      %span.cgray= user.to_reference
 
       - if user == current_user
-        %span.label.label-success It's you
+        %span.label.label-success.prepend-left-5 It's you
 
       - if user.blocked?
         %label.label.label-danger
           %strong Blocked
 
-      .cgray
+      - if source.instance_of?(Group) && !@group
+        = link_to source, class: "member-group-link prepend-left-5" do
+          = "· #{source.name}"
+
+      .hidden-xs.cgray
         - if member.request?
           Requested
           = time_ago_with_tooltip(member.requested_at)
@@ -73,20 +43,44 @@
           by
           = link_to member.created_by.name, user_path(member.created_by)
         = time_ago_with_tooltip(member.created_at)
-
   - if show_roles
-    .edit-member.hide.js-toggle-content
-      %br
-      = form_for member, remote: true, html: { class: 'form-horizontal' }  do |f|
-        .form-group
-          = label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label'
-          .col-sm-10
-            = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}"
-        .form-group
-          = label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label'
-          .col-sm-10
-            .clearable-input
-              = f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}"
+    .controls.member-controls
+      - if show_controls
+        - if user != current_user
+          = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
+            = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member
+            .prepend-left-5.clearable-input.member-form-control
+              = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member
               %i.clear-icon.js-clear-input
-        .prepend-top-10
-          = f.submit 'Save', class: 'btn btn-save btn-sm'
+        - else
+          %span.member-access-text= member.human_access
+
+        - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
+          = link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
+                    method: :post,
+                    class: 'btn btn-default  prepend-left-10'
+
+        - elsif member.request? && can_admin_member
+          = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
+                    method: :post,
+                    class: 'btn btn-success prepend-left-10',
+                    title: 'Grant access'
+
+        - if can?(current_user, action_member_permission(:destroy, member), member)
+          - if current_user == user
+            = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
+                      method: :delete,
+                      data: { confirm: leave_confirmation_message(member.source) },
+                      class: 'btn btn-remove prepend-left-10'
+          - else
+            = link_to member,
+                      remote: true,
+                      method: :delete,
+                      data: { confirm: remove_member_message(member) },
+                      class: 'btn btn-remove prepend-left-10',
+                      title: remove_member_title(member) do
+              %span.visible-xs-block
+                Delete
+              = icon('trash', class: 'hidden-xs')
+      - else
+        %span.member-access-text= member.human_access
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 40b39e850b00844cc1dd923452d6a6cde6567ef7..10050adfda5a4a59bbf8f369e4db0139da276300 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,8 +1,8 @@
 - if requesters.any?
   .panel.panel-default
     .panel-heading
+      Users requesting access to
       %strong= membership_source.name
-      access requests
       %span.badge= requesters.size
     %ul.content-list
       = render partial: 'shared/members/member', collection: requesters, as: :member
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 0c188478ed1f8e2475fed51edb7ba039e1f3d198..c6ca8314892d961d7e5ee6cd165f4e214b34e0a2 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -406,7 +406,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
         end
       end
 
-      resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ }
+      resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
 
       resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
         member do
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index 0c89a3db9ad1f0e09b5f44e285b135fba035824f..9396a76f0a288c7857433dc555f03b58562ac3cd 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -105,7 +105,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
       select "Developer", from: "access_level"
     end
 
-    click_button "Add users to group"
+    click_button "Add to group"
   end
 
   step 'I should see current user as "Developer"' do
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index d77945a6b9cd46b4ecb0a0bf33b5e52a021aaf37..2b8cd030acef899b1a5e4a2ad556c2516e5cd8cd 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -70,7 +70,7 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
       select "Developer", from: "access_level"
     end
 
-    click_button "Add users to project"
+    click_button "Add to project"
   end
 
   step 'I should see current user as "Developer"' do
diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb
index e9b45823c67a72e95476541fe638bb7962b8a532..cefc55d07abef198a32cf024f6302b61557eb3f4 100644
--- a/features/steps/group/members.rb
+++ b/features/steps/group/members.rb
@@ -1,4 +1,5 @@
 class Spinach::Features::GroupMembers < Spinach::FeatureSteps
+  include WaitForAjax
   include SharedAuthentication
   include SharedPaths
   include SharedGroup
@@ -13,7 +14,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
       select "Reporter", from: "access_level"
     end
 
-    click_button "Add users to group"
+    click_button "Add to group"
   end
 
   step 'I select "Mike" as "Master"' do
@@ -24,7 +25,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
       select "Master", from: "access_level"
     end
 
-    click_button "Add users to group"
+    click_button "Add to group"
   end
 
   step 'I should see "Mike" in team list as "Reporter"' do
@@ -47,7 +48,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
       select "Reporter", from: "access_level"
     end
 
-    click_button "Add users to group"
+    click_button "Add to group"
   end
 
   step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
@@ -66,7 +67,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
       select "Reporter", from: "access_level"
     end
 
-    click_button "Add users to group"
+    click_button "Add to group"
   end
 
   step 'I should see user "John Doe" in team list' do
@@ -108,7 +109,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
   step 'I search for \'Mary\' member' do
     page.within '.member-search-form' do
       fill_in 'search', with: 'Mary'
-      click_button 'Search'
+      find('.member-search-btn').click
     end
   end
 
@@ -116,9 +117,8 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
     member = mary_jane_member
 
     page.within "#group_member_#{member.id}" do
-      click_button 'Edit'
       select 'Developer', from: "member_access_level_#{member.id}"
-      click_on 'Save'
+      wait_for_ajax
     end
   end
 
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index e920f5a706ba44d60cfb5e78bcbab4f82cda9878..b21d0849ad1fc2a8954bafb18d6584850b607a72 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -22,7 +22,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
       select2(user.id, from: "#user_ids", multiple: true)
       select "Reporter", from: "access_level"
     end
-    click_button "Add users to project"
+    click_button "Add to project"
   end
 
   step 'I should see "Mike" in team list as "Reporter"' do
@@ -36,10 +36,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
 
   step 'I select "sjobs@apple.com" as "Reporter"' do
     page.within ".users-project-form" do
-      select2("sjobs@apple.com", from: "#user_ids", multiple: true)
+      find('#user_ids', visible: false).set('sjobs@apple.com')
       select "Reporter", from: "access_level"
     end
-    click_button "Add users to project"
+    click_button "Add to project"
   end
 
   step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
@@ -65,9 +65,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
     user = User.find_by(name: 'Dmitriy')
     project_member = project.project_members.find_by(user_id: user.id)
     page.within "#project_member_#{project_member.id}" do
-      click_button 'Edit'
       select "Reporter", from: "member_access_level_#{project_member.id}"
-      click_button "Save"
     end
   end
 
@@ -112,7 +110,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
   end
 
   step 'I click link "Import team from another project"' do
-    click_link "Import members from another project"
+    click_link "Import"
   end
 
   When 'I submit "Website" project for import team' do
@@ -144,8 +142,9 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
   end
 
   step 'I should see "Opensource" group user listing' do
-    expect(page).to have_content("Shared with OpenSource group, members with Master role (2)")
-    expect(page).to have_content(@os_user1.name)
-    expect(page).to have_content(@os_user2.name)
+    page.within '.project-members-groups' do
+      expect(page).to have_content('OpenSource')
+      expect(find('select').value).to eq('40')
+    end
   end
 end
diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb
index 10d3713f19f7264582226365d3f98888340df6fa..d811b05b0c3023e4cbb39bf47fb590cfb6e27c55 100644
--- a/spec/features/groups/members/owner_manages_access_requests_spec.rb
+++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb
@@ -41,7 +41,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
 
   def expect_visible_access_request(group, user)
     expect(group.requesters.exists?(user_id: user)).to be_truthy
-    expect(page).to have_content "#{group.name} access requests 1"
+    expect(page).to have_content "Users requesting access to #{group.name} 1"
     expect(page).to have_content user.name
   end
 end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cc2f695211ce3e44e220f3351dd0c8fb2f08aeb8
--- /dev/null
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Anonymous user sees members', feature: true, js: true do
+  include WaitForAjax
+
+  let(:user) { create(:user) }
+  let(:group) { create(:group, :public) }
+  let(:project) { create(:empty_project, :public) }
+
+  background do
+    project.team << [user, :master]
+    @group_link = create(:project_group_link, project: project, group: group)
+
+    login_as(user)
+    visit namespace_project_project_members_path(project.namespace, project)
+  end
+
+  it 'updates group access level' do
+    select 'Guest', from: "member_access_level_#{group.id}"
+    wait_for_ajax
+
+    visit namespace_project_project_members_path(project.namespace, project)
+
+    expect(page).to have_select("member_access_level_#{group.id}", selected: 'Guest')
+  end
+
+  it 'updates expiry date' do
+    tomorrow = Date.today + 3
+
+    fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F")
+    wait_for_ajax
+
+    page.within(find('li.group_member')) do
+      expect(page).to have_content('Expires in')
+    end
+  end
+
+  it 'deletes group link' do
+    page.within(first('.group_member')) do
+      find('.btn-remove').click
+    end
+    wait_for_ajax
+
+    expect(page).not_to have_selector('.group_member')
+  end
+
+  context 'search' do
+    it 'finds no results' do
+      page.within '.member-search-form' do
+        fill_in 'search', with: 'testing 123'
+        find('.member-search-btn').click
+      end
+
+      expect(page).not_to have_selector('.group_member')
+    end
+
+    it 'finds results' do
+      page.within '.member-search-form' do
+        fill_in 'search', with: group.name
+        find('.member-search-btn').click
+      end
+
+      expect(page).to have_selector('.group_member', count: 1)
+    end
+  end
+end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 430c384ac2ee824412a7051d55d80b927af3e6f5..27a83fdcd1f6d7235e5b82d52524e2af2ff3379e 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -1,6 +1,7 @@
 require 'spec_helper'
 
 feature 'Projects > Members > Master adds member with expiration date', feature: true, js: true do
+  include WaitForAjax
   include Select2Helper
   include ActiveSupport::Testing::TimeHelpers
 
@@ -20,7 +21,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
       page.within '.users-project-form' do
         select2(new_member.id, from: '#user_ids', multiple: true)
         fill_in 'expires_at', with: '2016-08-10'
-        click_on 'Add users to project'
+        click_on 'Add to project'
       end
 
       page.within '.project_member:first-child' do
@@ -35,9 +36,8 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
       visit namespace_project_project_members_path(project.namespace, project)
 
       page.within '.project_member:first-child' do
-        click_on 'Edit'
-        fill_in 'Access expiration date', with: '2016-08-09'
-        click_on 'Save'
+        find('.js-access-expiration-date').set '2016-08-09'
+        wait_for_ajax
         expect(page).to have_content('Expires in 3 days')
       end
     end
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index f7fcd9b67313be821404ec7c6ff9088e50588dec..d15376931c388d5ecd4cd021a83c367d78919346 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do
 
   def expect_visible_access_request(project, user)
     expect(project.requesters.exists?(user_id: user)).to be_truthy
-    expect(page).to have_content "#{project.name} access requests 1"
+    expect(page).to have_content "Users requesting access to #{project.name} 1"
     expect(page).to have_content user.name
   end
 end