diff --git a/src/components/select/functional-options.vue b/src/components/select/functional-options.vue
new file mode 100644
index 0000000..a319709
--- /dev/null
+++ b/src/components/select/functional-options.vue
@@ -0,0 +1,29 @@
+
+<script>
+    const returnArrayFn = () => [];
+
+    export default {
+        props: {
+            options: {
+                type: Array,
+                default: returnArrayFn
+            },
+            slotOptions: {
+                type: Array,
+                default: returnArrayFn
+            },
+            slotUpdateHook: {
+                type: Function,
+                default: () => {}
+            },
+        },
+        functional: true,
+        render(h, {props, parent}){
+            // to detect changes in the $slot children/options we do this hack
+            // so we can trigger the parents computed properties and have everything reactive
+            // although $slot.default is not
+            if (props.slotOptions !== parent.$slots.default) props.slotUpdateHook();
+            return props.options;
+        }
+    };
+</script>
diff --git a/src/components/select/option.vue b/src/components/select/option.vue
index 0733bb8..749bb0c 100644
--- a/src/components/select/option.vue
+++ b/src/components/select/option.vue
@@ -1,5 +1,5 @@
 <template>
-    <li :class="classes" @click.stop="select" @mouseout.stop="blur" v-show="!hidden"><slot>{{ showLabel }}</slot></li>
+    <li :class="classes" @click.stop="select"><slot>{{ showLabel }}</slot></li>
 </template>
 <script>
     import Emitter from '../../mixins/emitter';
@@ -22,15 +22,19 @@
             disabled: {
                 type: Boolean,
                 default: false
+            },
+            selected: {
+                type: Boolean,
+                default: false
+            },
+            isFocused: {
+                type: Boolean,
+                default: false
             }
         },
         data () {
             return {
-                selected: false,
-                index: 0,    // for up and down to focus
-                isFocus: false,
-                hidden: false,    // for search
-                searchLabel: '',    // the value is slot,only for search
+                searchLabel: '',  // the slot value (textContent)
                 autoComplete: false
             };
         },
@@ -41,53 +45,34 @@
                     {
                         [`${prefixCls}-disabled`]: this.disabled,
                         [`${prefixCls}-selected`]: this.selected && !this.autoComplete,
-                        [`${prefixCls}-focus`]: this.isFocus
+                        [`${prefixCls}-focus`]: this.isFocused
                     }
                 ];
             },
             showLabel () {
                 return (this.label) ? this.label : this.value;
+            },
+            optionLabel(){
+                return (this.$el && this.$el.textContent) || this.label;
             }
         },
         methods: {
             select () {
-                if (this.disabled) {
-                    return false;
-                }
+                if (this.disabled) return false;
 
-                this.dispatch('iSelect', 'on-select-selected', this.value);
+                this.dispatch('iSelect', 'on-select-selected', {
+                    value: this.value,
+                    label: this.optionLabel,
+                });
+                this.$emit('on-select-selected', {
+                    value: this.value,
+                    label: this.optionLabel,
+                });
             },
-            blur () {
-                this.isFocus = false;
-            },
-            queryChange (val) {
-                const parsedQuery = val.replace(/(\^|\(|\)|\[|\]|\$|\*|\+|\.|\?|\\|\{|\}|\|)/g, '\\$1');
-                this.hidden = !new RegExp(parsedQuery, 'i').test(this.searchLabel);
-            },
-            // 在使用函数防抖后,设置 key 后,不更新组件了,导致SearchLabel 不更新 #1865
-            updateSearchLabel () {
-                this.searchLabel = this.$el.textContent;
-            },
-            onSelectClose(){
-                this.isFocus = false;
-            },
-            onQueryChange(val){
-                this.queryChange(val);
-            }
         },
         mounted () {
-            this.updateSearchLabel();
-            this.dispatch('iSelect', 'append');
-            this.$on('on-select-close', this.onSelectClose);
-            this.$on('on-query-change',this.onQueryChange);
-
             const Select = findComponentUpward(this, 'iSelect');
             if (Select) this.autoComplete = Select.autoComplete;
         },
-        beforeDestroy () {
-            this.dispatch('iSelect', 'remove');
-            this.$off('on-select-close', this.onSelectClose);
-            this.$off('on-query-change',this.onQueryChange);
-        }
     };
 </script>
diff --git a/src/components/select/select-head.vue b/src/components/select/select-head.vue
new file mode 100644
index 0000000..f617ca5
--- /dev/null
+++ b/src/components/select/select-head.vue
@@ -0,0 +1,196 @@
+<template>
+    <div @click="onHeaderClick">
+        <div class="ivu-tag ivu-tag-checked" v-for="item in selectedMultiple">
+            <span class="ivu-tag-text">{{ item.label }}</span>
+            <Icon type="ios-close-empty" @click.native.stop="removeTag(item)"></Icon>
+        </div>
+        <span
+            :class="singleDisplayClasses"
+            v-show="singleDisplayValue"
+        >{{ singleDisplayValue }}</span>
+        <input
+            :id="inputElementId"
+            type="text"
+            v-if="filterable"
+            v-model="query"
+            :disabled="disabled"
+            :class="[prefixCls + '-input']"
+            :placeholder="showPlaceholder ? localePlaceholder : ''"
+            :style="inputStyle"
+            autocomplete="off"
+            spellcheck="false"
+            @keydown="resetInputState"
+            @keydown.delete="handleInputDelete"
+            @focus="onInputFocus"
+            @blur="onInputFocus"
+
+            ref="input">
+        <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-if="resetSelect" @click.native.stop="resetSelect"></Icon>
+        <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']" v-if="!resetSelect && !remote && !disabled"></Icon>
+    </div>
+</template>
+<script>
+    import Icon from '../icon';
+    import Emitter from '../../mixins/emitter';
+    import Locale from '../../mixins/locale';
+
+    const prefixCls = 'ivu-select';
+
+    export default {
+        name: 'iSelectHead',
+        mixins: [ Emitter, Locale ],
+        components: { Icon },
+        props: {
+            disabled: {
+                type: Boolean,
+                default: false
+            },
+            filterable: {
+                type: Boolean,
+                default: false
+            },
+            multiple: {
+                type: Boolean,
+                default: false
+            },
+            remote: {
+                type: Boolean,
+                default: false
+            },
+            initialLabel: {
+                type: String,
+            },
+            values: {
+                type: Array,
+                default: () => []
+            },
+            clearable: {
+                type: [Function, Boolean],
+                default: false,
+            },
+            inputElementId: {
+                type: String
+            },
+            placeholder: {
+                type: String
+            },
+            queryProp: {
+                type: String,
+                default: ''
+            }
+        },
+        data () {
+            return {
+                prefixCls: prefixCls,
+                query: '',
+                inputLength: 20,
+                remoteInitialLabel: this.initialLabel,
+                preventRemoteCall: false,
+            };
+        },
+        computed: {
+            singleDisplayClasses(){
+                const {filterable, multiple, showPlaceholder} = this;
+                return [{
+                    [prefixCls + '-placeholder']: showPlaceholder && !filterable,
+                    [prefixCls + '-selected-value']: !showPlaceholder && !multiple && !filterable,
+                }];
+            },
+            singleDisplayValue(){
+                if ((this.multiple && this.values.length > 0) || this.filterable) return '';
+                return `${this.selectedSingle}` || this.localePlaceholder;
+            },
+            showPlaceholder () {
+                let status = false;
+                if (!this.multiple) {
+                    const value = this.values[0];
+                    if (typeof value === 'undefined' || String(value).trim() === ''){
+                        status = !this.remoteInitialLabel;
+                    }
+                } else {
+                    if (!this.values.length > 0) {
+                        status = true;
+                    }
+                }
+                return status;
+            },
+            resetSelect(){
+                return !this.showPlaceholder && this.clearable;
+            },
+            inputStyle () {
+                let style = {};
+
+                if (this.multiple) {
+                    if (this.showPlaceholder) {
+                        style.width = '100%';
+                    } else {
+                        style.width = `${this.inputLength}px`;
+                    }
+                }
+
+                return style;
+            },
+            localePlaceholder () {
+                if (this.placeholder === undefined) {
+                    return this.t('i.select.placeholder');
+                } else {
+                    return this.placeholder;
+                }
+            },
+            selectedSingle(){
+                const selected = this.values[0];
+                return selected ? selected.label : (this.remoteInitialLabel || '');
+            },
+            selectedMultiple(){
+                return this.multiple ? this.values : [];
+            }
+        },
+        methods: {
+            onInputFocus(e){
+                this.$emit(e.type === 'focus' ? 'on-input-focus' : 'on-input-blur');
+            },
+            removeTag (value) {
+                if (this.disabled) return false;
+                this.dispatch('iSelect', 'on-select-selected', value);
+            },
+            resetInputState () {
+                this.inputLength = this.$refs.input.value.length * 12 + 20;
+            },
+            handleInputDelete () {
+                if (this.multiple && this.selectedMultiple.length && this.query === '') {
+                    this.removeTag(this.selectedMultiple[this.selectedMultiple.length - 1]);
+                }
+            },
+            onHeaderClick(e){
+                if (this.filterable && e.target === this.$el){
+                    this.$refs.input.focus();
+                }
+            }
+        },
+        watch: {
+            values ([value]) {
+                if (!this.filterable) return;
+                this.preventRemoteCall = true;
+                if (this.multiple){
+                    this.query = '';
+                    this.preventRemoteCall = false; // this should be after the query change setter above
+                    return;
+                }
+                // #982
+                if (typeof value === 'undefined' || value === '' || value === null) this.query = '';
+                else this.query = value.label;
+            },
+            query (val) {
+                if (this.preventRemoteCall) {
+                    this.preventRemoteCall = false;
+                    return;
+                }
+
+                this.$emit('on-query-change', val);
+            },
+            queryProp(query){
+                if (query !== this.query) this.query = query;
+            },
+        }
+    };
+</script>
diff --git a/src/components/select/select.vue b/src/components/select/select.vue
index 7a8ea3f..ba98364 100644
--- a/src/components/select/select.vue
+++ b/src/components/select/select.vue
@@ -1,38 +1,50 @@
 <template>
     <div
-        tabindex="0"
-        @keydown.down="handleFocus"
         :class="classes"
-        v-clickoutside="handleClose">
+        v-click-outside.capture="onClickOutside"
+    >
         <div
-            :class="selectionCls"
             ref="reference"
-            @click="toggleMenu">
+
+            :class="selectionCls"
+            :tabindex="selectTabindex"
+
+            @blur="toggleHeaderFocus"
+            @focus="toggleHeaderFocus"
+
+            @click="toggleMenu"
+            @keydown.esc="handleKeydown"
+            @keydown.enter="handleKeydown"
+            @keydown.up="handleKeydown"
+            @keydown.down="handleKeydown"
+            @keydown.tab="handleKeydown"
+            @keydown.delete="handleKeydown"
+
+
+            @mouseenter="hasMouseHoverHead = true"
+            @mouseleave="hasMouseHoverHead = false"
+
+        >
             <slot name="input">
-                <input type="hidden" :name="name" :value="model">
-                <div class="ivu-tag ivu-tag-checked" v-for="(item, index) in selectedMultiple">
-                    <span class="ivu-tag-text">{{ item.label }}</span>
-                    <Icon type="ios-close-empty" @click.native.stop="removeTag(index)"></Icon>
-                </div>
-                <span :class="[prefixCls + '-placeholder']" v-show="showPlaceholder && !filterable">{{ localePlaceholder }}</span>
-                <span :class="[prefixCls + '-selected-value']" v-show="!showPlaceholder && !multiple && !filterable">{{ selectedSingle }}</span>
-                <input
-                    :id="elementId"
-                    type="text"
-                    v-if="filterable"
-                    v-model="query"
+                <input type="hidden" :name="name" :value="publicValue">
+                <select-head
+                    :filterable="filterable"
+                    :multiple="multiple"
+                    :values="values"
+                    :clearable="canBeCleared"
                     :disabled="disabled"
-                    :class="[prefixCls + '-input']"
-                    :placeholder="showPlaceholder ? localePlaceholder : ''"
-                    :style="inputStyle"
-                    autocomplete="off"
-                    spellcheck="false"
-                    @blur="handleBlur"
-                    @keydown="resetInputState"
-                    @keydown.delete="handleInputDelete"
-                    ref="input">
-                <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSingleSelect"></Icon>
-                <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']" v-if="!remote"></Icon>
+                    :remote="remote"
+                    :input-element-id="elementId"
+                    :initial-label="initialLabel"
+                    :placeholder="placeholder"
+                    :query-prop="query"
+
+                    @on-query-change="onQueryChange"
+                    @on-input-focus="isFocused = true"
+                    @on-input-blur="isFocused = false"
+
+                    ref="selectHead"
+                />
             </slot>
         </div>
         <transition name="transition-drop">
@@ -42,9 +54,17 @@
                 :placement="placement"
                 ref="dropdown"
                 :data-transfer="transfer"
-                v-transfer-dom>
-                <ul v-show="notFoundShow" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
-                <ul v-show="(!notFound && !remote) || (remote && !loading && !notFound)" :class="[prefixCls + '-dropdown-list']"><slot></slot></ul>
+                v-transfer-dom
+            >
+                <ul v-show="showNotFoundLabel" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
+                <ul :class="prefixCls + '-dropdown-list'">
+                  <functional-options
+                      v-if="(!remote) || (remote && !loading)"
+                      :options="selectOptions"
+                      :slot-update-hook="updateSlotOptions"
+                      :slot-options="slotOptions"
+                  ></functional-options>
+                </ul>
                 <ul v-show="loading" :class="[prefixCls + '-loading']">{{ localeLoadingText }}</ul>
             </Drop>
         </transition>
@@ -53,20 +73,32 @@
 <script>
     import Icon from '../icon';
     import Drop from './dropdown.vue';
-    import clickoutside from '../../directives/clickoutside';
+    import vClickOutside from 'v-click-outside-x/index';
     import TransferDom from '../../directives/transfer-dom';
-    import { oneOf, findComponentDownward } from '../../utils/assist';
+    import { oneOf } from '../../utils/assist';
     import Emitter from '../../mixins/emitter';
     import Locale from '../../mixins/locale';
-    import { debounce } from './utils';
+    import SelectHead from './select-head.vue';
+    import FunctionalOptions from './functional-options.vue';
 
     const prefixCls = 'ivu-select';
+    const optionGroupRegexp = /option\-?group/i;
+
+    const findChild = (instance, checkFn) => {
+        let match = checkFn(instance);
+        if (match) return instance;
+        for (let i = 0, l = instance.$children.length; i < l; i++){
+            const child = instance.$children[i];
+            match = findChild(child, checkFn);
+            if (match) return match;
+        }
+    };
 
     export default {
         name: 'iSelect',
         mixins: [ Emitter, Locale ],
-        components: { Icon, Drop },
-        directives: { clickoutside, TransferDom },
+        components: { FunctionalOptions, Drop, Icon, SelectHead },
+        directives: { clickOutside: vClickOutside.directive, TransferDom },
         props: {
             value: {
                 type: [String, Number, Array],
@@ -99,10 +131,6 @@
             filterMethod: {
                 type: Function
             },
-            remote: {
-                type: Boolean,
-                default: false
-            },
             remoteMethod: {
                 type: Function
             },
@@ -147,23 +175,29 @@
                 type: String
             }
         },
+        mounted(){
+            this.$on('on-select-selected', this.onOptionClick);
+
+            // set the initial values if there are any
+            if (this.values.length > 0 && !this.remote){
+                this.values = this.values.map(this.getOptionData);
+            }
+        },
         data () {
+
             return {
                 prefixCls: prefixCls,
+                values: this.getInitialValue(),
+                dropDownWidth: 0,
                 visible: false,
-                options: [],
-                optionInstances: [],
-                selectedSingle: '',    // label
-                selectedMultiple: [],
-                focusIndex: 0,
+                focusIndex: -1,
+                isFocused: false,
                 query: '',
-                lastQuery: '',
-                selectToChangeQuery: false,    // when select an option, set this first and set query, because query is watching, it will emit event
-                inputLength: 20,
-                notFound: false,
-                slotChangeDuration: false,    // if slot change duration and in multiple, set true and after slot change, set false
-                model: this.value,
-                currentLabel: this.label
+                initialLabel: this.label,
+                hasMouseHoverHead: false,
+                slotOptions: this.$slots.default,
+                caretPosition: -1,
+                lastRemoteQuery: '',
             };
         },
         computed: {
@@ -189,58 +223,19 @@
             },
             selectionCls () {
                 return {
-                    [`${prefixCls}-selection`]: !this.autoComplete
+                    [`${prefixCls}-selection`]: !this.autoComplete,
+                    [`${prefixCls}-selection-focused`]: this.isFocused
                 };
             },
-            showPlaceholder () {
-                let status = false;
-
-                if ((typeof this.model) === 'string') {
-                    if (this.model === '') {
-                        status = true;
-                    }
-                } else if (Array.isArray(this.model)) {
-                    if (!this.model.length) {
-                        status = true;
-                    }
-                } else if( this.model === null){
-                    status = true;
-                }
-
-                return status;
-            },
-            showCloseIcon () {
-                return !this.multiple && this.clearable && !this.showPlaceholder;
-            },
-            inputStyle () {
-                let style = {};
-
-                if (this.multiple) {
-                    if (this.showPlaceholder) {
-                        style.width = '100%';
-                    } else {
-                        style.width = `${this.inputLength}px`;
-                    }
-                }
-
-                return style;
-            },
-            localePlaceholder () {
-                if (this.placeholder === undefined) {
-                    return this.t('i.select.placeholder');
-                } else {
-                    return this.placeholder;
-                }
-            },
             localeNotFoundText () {
-                if (this.notFoundText === undefined) {
+                if (typeof this.notFoundText === 'undefined') {
                     return this.t('i.select.noMatch');
                 } else {
                     return this.notFoundText;
                 }
             },
             localeLoadingText () {
-                if (this.loadingText === undefined) {
+                if (typeof this.loadingText === 'undefined') {
                     return this.t('i.select.loading');
                 } else {
                     return this.loadingText;
@@ -251,574 +246,358 @@
             },
             dropVisible () {
                 let status = true;
-                const options = this.$slots.default || [];
+                const options = this.selectOptions;
                 if (!this.loading && this.remote && this.query === '' && !options.length) status = false;
 
                 if (this.autoComplete && !options.length) status = false;
 
                 return this.visible && status;
             },
-            notFoundShow () {
-                const options = this.$slots.default || [];
-                return (this.notFound && !this.remote) || (this.remote && !this.loading && !options.length);
-            }
-        },
-        methods: {
-            // open when focus on Select and press `down` key
-            handleFocus () {
-                if (!this.visible) this.toggleMenu();
-            },
-            toggleMenu () {
-                if (this.disabled || this.autoComplete) {
-                    return false;
-                }
-                this.visible = !this.visible;
+            showNotFoundLabel () {
+                const {loading, remote, selectOptions} = this;
+                return selectOptions.length === 0 && (!remote || (remote && !loading));
             },
-            hideMenu () {
-                this.visible = false;
-                this.focusIndex = 0;
-                this.broadcast('iOption', 'on-select-close');
-            },
-            // find option component
-            findChild (cb) {
-                const find = function (child) {
-                    const name = child.$options.componentName;
-
-                    if (name) {
-                        cb(child);
-                    } else if (child.$children.length) {
-                        child.$children.forEach((innerChild) => {
-                            find(innerChild, cb);
-                        });
-                    }
-                };
-
-                if (this.optionInstances.length) {
-                    this.optionInstances.forEach((child) => {
-                        find(child);
-                    });
+            publicValue(){
+                if (this.labelInValue){
+                    return this.multiple ? this.values : this.values[0];
                 } else {
-                    this.$children.forEach((child) => {
-                        find(child);
-                    });
+                    return this.multiple ? this.values.map(option => option.value) : (this.values[0] || {}).value;
                 }
             },
-            updateOptions (slot = false) {
-                let options = [];
-                let index = 1;
-
-                this.findChild((child) => {
-                    options.push({
-                        value: child.value,
-                        label: (child.label === undefined) ? child.$el.textContent : child.label
-                    });
-                    child.index = index++;
+            canBeCleared(){
+                const uiStateMatch = this.hasMouseHoverHead || this.active;
+                const qualifiesForClear = !this.multiple && this.clearable;
+                return uiStateMatch && qualifiesForClear && this.reset; // we return a function
+            },
+            selectOptions() {
+                const selectOptions = [];
+                let optionCounter = -1;
+                const currentIndex = this.focusIndex;
+                const selectedValues = this.values.map(({value}) => value);
+                for (let option of (this.slotOptions || [])) {
 
-                    this.optionInstances.push(child);
-                });
+                    if (!option.componentOptions) continue;
 
-                this.options = options;
+                    if (option.componentOptions.tag.match(optionGroupRegexp)){
+                        let children = option.componentOptions.children;
 
-                if (!this.remote) {
-                    this.updateSingleSelected(true, slot);
-                    this.updateMultipleSelected(true, slot);
-                }
-            },
-            updateSingleSelected (init = false, slot = false) {
-                const type = typeof this.model;
+                        // remove filtered children
+                        if (this.filterable){
+                            children = children.filter(
+                                ({componentOptions}) => this.validateOption(componentOptions)
+                            );
+                        }
 
-                if (type === 'string' || type === 'number') {
-                    let findModel = false;
+                        option.componentOptions.children = children.map(opt => {
+                            optionCounter = optionCounter + 1;
+                            return this.processOption(opt, selectedValues, optionCounter === currentIndex);
+                        });
 
-                    for (let i = 0; i < this.options.length; i++) {
-                        if (this.model === this.options[i].value) {
-                            this.selectedSingle = this.options[i].label;
-                            findModel = true;
-                            break;
-                        }
-                    }
+                        // keep the group if it still has children
+                        if (option.componentOptions.children.length > 0) selectOptions.push({...option});
+                    } else {
+                        // ignore option if not passing filter
+                        const optionPassesFilter = this.filterable ? this.validateOption(option.componentOptions) : option;
+                        if (!optionPassesFilter) continue;
 
-                    if (slot && !findModel) {
-                        this.model = '';
-                        this.query = '';
+                        optionCounter = optionCounter + 1;
+                        selectOptions.push(this.processOption(option, selectedValues, optionCounter === currentIndex));
                     }
                 }
 
-                this.toggleSingleSelected(this.model, init);
+                return selectOptions;
             },
-            clearSingleSelect () {
-                if (this.showCloseIcon) {
-                    this.findChild((child) => {
-                        child.selected = false;
-                    });
-                    this.model = '';
-
-                    if (this.filterable) {
-                        this.query = '';
-                    }
+            flatOptions(){
+                return this.selectOptions.reduce((options, option) => {
+                    const isOptionGroup = option.componentOptions.tag.match(optionGroupRegexp);
+                    if (isOptionGroup) return options.concat(option.componentOptions.children || []);
+                    else return options.concat(option);
+                }, []);
+            },
+            selectTabindex(){
+                return this.disabled || this.filterable ? -1 : 0;
+            },
+            remote(){
+                return typeof this.remoteMethod === 'function';
+            }
+        },
+        methods: {
+            setQuery(query){ // PUBLIC API
+                if (query) {
+                    this.onQueryChange(query);
+                    return;
+                }
+                if (query === null) {
+                    this.onQueryChange('');
+                    this.values = [];
                 }
             },
-            updateMultipleSelected (init = false, slot = false) {
-                if (this.multiple && Array.isArray(this.model)) {
-                    let selected = this.remote ? this.selectedMultiple : [];
-
-                    for (let i = 0; i < this.model.length; i++) {
-                        const model = this.model[i];
-
-                        for (let j = 0; j < this.options.length; j++) {
-                            const option = this.options[j];
+            clearSingleSelect(){ // PUBLIC API
+                if (this.clearable) this.values = [];
+            },
+            getOptionData(value){
+                const option = this.flatOptions.find(({componentOptions}) => componentOptions.propsData.value === value);
+                const textContent = option.componentOptions.children.reduce((str, child) => str + (child.text || ''), '');
+                const label = option.componentOptions.propsData.label || textContent || '';
+                return {
+                    value: value,
+                    label: label,
+                };
+            },
+            getInitialValue(){
+                const {multiple, value} = this;
+                let initialValue = Array.isArray(value) ? value : [value];
+                if (!multiple && (typeof initialValue[0] === 'undefined' || String(initialValue[0]).trim() === '')) initialValue = [];
+                return initialValue;
+            },
+            processOption(option, values, isFocused){
+                if (!option.componentOptions) return option;
+                const optionValue = option.componentOptions.propsData.value;
+                const disabled = option.componentOptions.propsData.disabled;
+                const isSelected = values.includes(optionValue);
+
+                const propsData = {
+                    ...option.componentOptions.propsData,
+                    selected: isSelected,
+                    isFocused: isFocused,
+                    disabled: typeof disabled === 'undefined' ? false : disabled !== false,
+                };
 
-                            if (model === option.value) {
-                                selected.push({
-                                    value: option.value,
-                                    label: option.label
-                                });
-                            }
-                        }
+                return {
+                    ...option,
+                    componentOptions: {
+                        ...option.componentOptions,
+                        propsData: propsData
                     }
+                };
+            },
 
-                    const selectedArray = [];
-                    const selectedObject = {};
-
-                    selected.forEach(item => {
-                        if (!selectedObject[item.value]) {
-                            selectedArray.push(item);
-                            selectedObject[item.value] = 1;
-                        }
-                    });
-
-                    // #2066
-                    this.selectedMultiple = this.remote ? this.model.length ? selectedArray : [] : selected;
-
-                    if (slot) {
-                        let selectedModel = [];
-
-                        for (let i = 0; i < selected.length; i++) {
-                            selectedModel.push(selected[i].value);
-                        }
-
-                        // if slot change and remove a selected option, emit user
-                        if (this.model.length === selectedModel.length) {
-                            this.slotChangeDuration = true;
-                        }
-
-                        this.model = selectedModel;
-                    }
-                }
-                this.toggleMultipleSelected(this.model, init);
+            validateOption({elm, propsData}){
+                const value = propsData.value;
+                const label = propsData.label || '';
+                const textContent = elm && elm.textContent || '';
+                const stringValues = JSON.stringify([value, label, textContent]);
+                return stringValues.toLowerCase().includes(this.query.toLowerCase());
             },
-            removeTag (index) {
-                if (this.disabled) {
-                    return false;
-                }
 
-                if (this.remote) {
-                    const tag = this.model[index];
-                    this.selectedMultiple = this.selectedMultiple.filter(item => item.value !== tag);
+            toggleMenu (e, force) {
+                if (this.disabled || this.autoComplete) {
+                    return false;
                 }
+                this.focusIndex = -1;
 
-                this.model.splice(index, 1);
-
-                if (this.filterable && this.visible) {
-                    this.$refs.input.focus();
+                this.visible = typeof force !== 'undefined' ? force : !this.visible;
+                if (this.visible){
+                    this.dropDownWidth = this.$el.getBoundingClientRect().width;
                 }
-
-                this.broadcast('Drop', 'on-update-popper');
             },
-            // to select option for single
-            toggleSingleSelected (value, init = false) {
-                if (!this.multiple) {
-                    let label = '';
-
-                    this.findChild((child) => {
-                        if (child.value === value) {
-                            child.selected = true;
-                            label = (child.label === undefined) ? child.$el.innerHTML : child.label;
-                        } else {
-                            child.selected = false;
-                        }
-                    });
-
-                    this.hideMenu();
-
-                    if (!init) {
-                        if (this.labelInValue) {
-                            this.$emit('on-change', {
-                                value: value,
-                                label: label
-                            });
-                            this.dispatch('FormItem', 'on-form-change', {
-                                value: value,
-                                label: label
-                            });
-                        } else {
-                            this.$emit('on-change', value);
-                            this.dispatch('FormItem', 'on-form-change', value);
-                        }
-                    }
-                }
+            hideMenu () {
+                this.toggleMenu(null, false);
             },
-            // to select option for multiple
-            toggleMultipleSelected (value, init = false) {
-                if (this.multiple) {
-                    let hybridValue = [];
-                    for (let i = 0; i < value.length; i++) {
-                        hybridValue.push({
-                            value: value[i]
+            onClickOutside(event){
+                if (this.visible) {
+
+                    if (this.filterable) {
+                        const input = this.$refs.selectHead.$refs.input;
+                        this.caretPosition = input.selectionStart;
+                        this.$nextTick(() => {
+                            const caretPosition = this.caretPosition === -1 ? input.value.length : this.caretPosition;
+                            input.setSelectionRange(caretPosition, caretPosition);
                         });
                     }
 
-                    this.findChild((child) => {
-                        const index = value.indexOf(child.value);
-
-                        if (index >= 0) {
-                            child.selected = true;
-                            hybridValue[index].label = (child.label === undefined) ? child.$el.innerHTML : child.label;
-                        } else {
-                            child.selected = false;
-                        }
-                    });
-
-                    if (!init) {
-                        if (this.labelInValue) {
-                            this.$emit('on-change', hybridValue);
-                            this.dispatch('FormItem', 'on-form-change', hybridValue);
-                        } else {
-                            this.$emit('on-change', value);
-                            this.dispatch('FormItem', 'on-form-change', value);
-                        }
-                    }
+                    event.stopPropagation();
+                    event.preventDefault();
+                    this.hideMenu();
+                    this.isFocused = true;
+                } else {
+                    this.caretPosition = -1;
+                    this.isFocused = false;
                 }
             },
-            handleClose () {
-                this.hideMenu();
+            reset(){
+                this.values = [];
             },
             handleKeydown (e) {
+                if (e.key === 'Backspace'){
+                    return; // so we don't call preventDefault
+                }
+
                 if (this.visible) {
-                    const keyCode = e.keyCode;
+                    e.preventDefault();
+                    if (e.key === 'Tab'){
+                        e.stopPropagation();
+                    }
+
                     // Esc slide-up
-                    if (keyCode === 27) {
-                        e.preventDefault();
+                    if (e.key === 'Escape') {
                         this.hideMenu();
                     }
                     // next
-                    if (keyCode === 40) {
-                        e.preventDefault();
-                        this.navigateOptions('next');
+                    if (e.key === 'ArrowUp') {
+                        this.navigateOptions(-1);
                     }
                     // prev
-                    if (keyCode === 38) {
-                        e.preventDefault();
-                        this.navigateOptions('prev');
+                    if (e.key === 'ArrowDown') {
+                        this.navigateOptions(1);
                     }
                     // enter
-                    if (keyCode === 13) {
-                        e.preventDefault();
-
-                        this.findChild((child) => {
-                            if (child.isFocus) {
-                                child.select();
-                            }
-                        });
+                    if (e.key === 'Enter' && this.focusIndex > -1) {
+                        const optionComponent = this.flatOptions[this.focusIndex];
+                        const option = this.getOptionData(optionComponent.componentOptions.propsData.value);
+                        this.onOptionClick(option);
                     }
-                }
-            },
-            navigateOptions (direction) {
-                if (direction === 'next') {
-                    const next = this.focusIndex + 1;
-                    this.focusIndex = (this.focusIndex === this.options.length) ? 1 : next;
-                } else if (direction === 'prev') {
-                    const prev = this.focusIndex - 1;
-                    this.focusIndex = (this.focusIndex <= 1) ? this.options.length : prev;
+                } else {
+                    const keysThatCanOpenSelect = ['ArrowUp', 'ArrowDown'];
+                    if (keysThatCanOpenSelect.includes(e.key)) this.toggleMenu(null, true);
                 }
 
-                let child_status = {
-                    disabled: false,
-                    hidden: false
-                };
 
-                let find_deep = false;    // can next find allowed
+            },
+            navigateOptions(direction){
+                const optionsLength = this.flatOptions.length - 1;
 
-                this.findChild((child) => {
-                    if (child.index === this.focusIndex) {
-                        child_status.disabled = child.disabled;
-                        child_status.hidden = child.hidden;
+                let index = this.focusIndex + direction;
+                if (index < 0) index = optionsLength;
+                if (index > optionsLength) index = 0;
 
-                        if (!child.disabled && !child.hidden) {
-                            child.isFocus = true;
-                        }
-                    } else {
-                        child.isFocus = false;
+                // find nearest option in case of disabled options in between
+                if (direction > 0){
+                    let nearestActiveOption = -1;
+                    for (let i = 0; i < this.flatOptions.length; i++){
+                        const optionIsActive = !this.flatOptions[i].componentOptions.propsData.disabled;
+                        if (optionIsActive) nearestActiveOption = i;
+                        if (nearestActiveOption >= index) break;
                     }
-
-                    if (!child.hidden && !child.disabled) {
-                        find_deep = true;
+                    index = nearestActiveOption;
+                } else {
+                    let nearestActiveOption = this.flatOptions.length;
+                    for (let i = optionsLength; i >= 0; i--){
+                        const optionIsActive = !this.flatOptions[i].componentOptions.propsData.disabled;
+                        if (optionIsActive) nearestActiveOption = i;
+                        if (nearestActiveOption <= index) break;
                     }
-                });
-
-                this.resetScrollTop();
-
-                if ((child_status.disabled || child_status.hidden) && find_deep) {
-                    this.navigateOptions(direction);
+                    index = nearestActiveOption;
                 }
-            },
-            resetScrollTop () {
-                const index = this.focusIndex - 1;
-                if (!this.optionInstances.length) return;
-                let bottomOverflowDistance = this.optionInstances[index].$el.getBoundingClientRect().bottom - this.$refs.dropdown.$el.getBoundingClientRect().bottom;
-                let topOverflowDistance = this.optionInstances[index].$el.getBoundingClientRect().top - this.$refs.dropdown.$el.getBoundingClientRect().top;
 
-                if (bottomOverflowDistance > 0) {
-                    this.$refs.dropdown.$el.scrollTop += bottomOverflowDistance;
-                }
-                if (topOverflowDistance < 0) {
-                    this.$refs.dropdown.$el.scrollTop += topOverflowDistance;
-                }
+                this.focusIndex = index;
             },
-            handleBlur () {
-                setTimeout(() => {
-                    if (this.autoComplete) return;
-                    const model = this.model;
+            onOptionClick(option) {
+                if (this.multiple){
+
+                    // keep the query for remote select
+                    if (this.remote) this.lastRemoteQuery = this.lastRemoteQuery || this.query;
+                    else this.lastRemoteQuery = '';
 
-                    if (this.multiple) {
-                        this.query = '';
+                    const valueIsSelected = this.values.find(({value}) => value === option.value);
+                    if (valueIsSelected){
+                        this.values = this.values.filter(({value}) => value !== option.value);
                     } else {
-                        if (model !== '') {
-                            this.findChild((child) => {
-                                if (child.value === model) {
-                                    this.query = child.label === undefined ? child.searchLabel : child.label;
-                                }
-                            });
-                            // 如果删除了搜索词,下拉列表也清空了,所以强制调用一次remoteMethod
-                            if (this.remote && this.query !== this.lastQuery) {
-                                this.$nextTick(() => {
-                                    this.query = this.lastQuery;
-                                });
-                            }
-                        } else {
-                            this.query = '';
-                        }
+                        this.values = this.values.concat(option);
                     }
-                }, 300);
-            },
-            resetInputState () {
-                this.inputLength = this.$refs.input.value.length * 12 + 20;
-            },
-            handleInputDelete () {
-                if (this.multiple && this.model.length && this.query === '') {
-                    this.removeTag(this.model.length - 1);
+
+                    this.isFocused = true; // so we put back focus after clicking with mouse on option elements
+                } else {
+                    this.values = [option];
+                    this.lastRemoteQuery = '';
+                    this.hideMenu();
+                }
+
+                if (this.filterable){
+                    const inputField = this.$refs.selectHead.$refs.input;
+                    this.$nextTick(() => inputField.focus());
                 }
             },
-            // use when slot changed
-            slotChange () {
-                this.options = [];
-                this.optionInstances = [];
-            },
-            setQuery (query) {
-                if (!this.filterable) return;
+            onQueryChange(query) {
                 this.query = query;
+                if (this.query.length > 0) this.visible = true;
             },
-            modelToQuery() {
-                if (!this.multiple && this.filterable && this.model !== undefined) {
-                    this.findChild((child) => {
-                        if (this.model === child.value) {
-                            if (child.label) {
-                                this.query = child.label;
-                            } else if (child.searchLabel) {
-                                this.query = child.searchLabel;
-                            } else {
-                                this.query = child.value;
-                            }
-                        }
-                    });
-                }
-            },
-            broadcastQuery (val) {
-                if (findComponentDownward(this, 'OptionGroup')) {
-                    this.broadcast('OptionGroup', 'on-query-change', val);
-                    this.broadcast('iOption', 'on-query-change', val);
-                } else {
-                    this.broadcast('iOption', 'on-query-change', val);
+            toggleHeaderFocus({type}){
+                if (this.disabled) {
+                    return;
                 }
+                this.isFocused = type === 'focus';
             },
-            debouncedAppendRemove(){
-                return debounce(function(){
-                    if (!this.remote) {
-                        this.modelToQuery();
-                        this.$nextTick(() => this.broadcastQuery(''));
-                    } else {
-                        this.findChild((child) => {
-                            child.updateSearchLabel();   // #1865
-                            child.selected = this.multiple ? this.model.indexOf(child.value) > -1 : this.model === child.value;
-                        });
-                    }
-                    this.slotChange();
-                    this.updateOptions(true);
-                });
-            },
-            // 处理 remote 初始值
-            updateLabel () {
-                if (this.remote) {
-                    if (!this.multiple && this.model !== '') {
-                        this.selectToChangeQuery = true;
-                        if (this.currentLabel === '') this.currentLabel = this.model;
-                        this.lastQuery = this.currentLabel;
-                        this.query = this.currentLabel;
-                    } else if (this.multiple && this.model.length) {
-                        if (this.currentLabel.length !== this.model.length) this.currentLabel = this.model;
-                        this.selectedMultiple = this.model.map((item, index) => {
-                            return {
-                                value: item,
-                                label: this.currentLabel[index]
-                            };
-                        });
-                    } else if (this.multiple && !this.model.length) {
-                        this.selectedMultiple = [];
-                    }
-                }
+            updateSlotOptions(){
+                this.slotOptions = this.$slots.default;
             }
         },
-        mounted () {
-            this.modelToQuery();
-            // 处理 remote 初始值
-            this.updateLabel();
-            this.$nextTick(() => {
-                this.broadcastQuery('');
-            });
-
-            this.updateOptions();
-            document.addEventListener('keydown', this.handleKeydown);
-
-            this.$on('append', this.debouncedAppendRemove());
-            this.$on('remove', this.debouncedAppendRemove());
-
-            this.$on('on-select-selected', (value) => {
-                if (this.model === value) {
-                    if (this.autoComplete) this.$emit('on-change', value);
-                    this.hideMenu();
-                } else {
-                    if (this.multiple) {
-                        const index = this.model.indexOf(value);
-                        if (index >= 0) {
-                            this.removeTag(index);
-                        } else {
-                            this.model.push(value);
-                            this.broadcast('Drop', 'on-update-popper');
-                        }
-
-                        if (this.filterable) {
-                            // remote&filterable&multiple时,一次点多项,不应该设置true,因为无法置为false,下次的搜索会失效
-                            if (this.query !== '') this.selectToChangeQuery = true;
-                            this.query = '';
-                            this.$refs.input.focus();
-                        }
-                    } else {
-                        this.model = value;
-
-                        if (this.filterable) {
-                            this.findChild((child) => {
-                                if (child.value === value) {
-                                    if (this.query !== '') this.selectToChangeQuery = true;
-                                    this.lastQuery = this.query = child.label === undefined ? child.searchLabel : child.label;
-                                }
-                            });
-                        }
-                    }
-                }
-            });
-        },
-        beforeDestroy () {
-            document.removeEventListener('keydown', this.handleKeydown);
-        },
         watch: {
-            value (val) {
-                this.model = val;
-                // #982
-                if (val === '' || val === null) this.query = '';
-            },
-            label (val) {
-                this.currentLabel = val;
-                this.updateLabel();
-            },
-            model () {
-                this.$emit('input', this.model);
-                this.modelToQuery();
-                if (this.multiple) {
-                    if (this.slotChangeDuration) {
-                        this.slotChangeDuration = false;
-                    } else {
-                        this.updateMultipleSelected();
-                    }
-                } else {
-                    this.updateSingleSelected();
+            value(value){
+                const {getInitialValue, getOptionData, publicValue} = this;
+
+                if (value === '') this.values = [];
+                else if (JSON.stringify(value) !== JSON.stringify(publicValue)) {
+                    this.$nextTick(() => this.values = getInitialValue().map(getOptionData));
                 }
-                // #957
-                if (!this.visible && this.filterable) {
-                    this.$nextTick(() => {
-                        this.broadcastQuery('');
-                    });
+            },
+            values(now, before){
+                const newValue = JSON.stringify(now);
+                const oldValue = JSON.stringify(before);
+                const shouldEmitInput = newValue !== oldValue;
+
+                if (shouldEmitInput) {
+                    // v-model is always just the value, event with labelInValue === true
+                    const vModelValue = this.labelInValue ?
+                        (this.multiple ? this.publicValue.map(({value}) => value)
+                        :
+                        this.publicValue.value) : this.publicValue;
+                    this.$emit('input', vModelValue); // to update v-model
+                    this.$emit('on-change', this.publicValue);
+                    this.dispatch('FormItem', 'on-form-change', this.publicValue);
                 }
             },
-            visible (val) {
-                if (val) {
-                    if (this.filterable) {
-                        if (this.multiple) {
-                            this.$refs.input.focus();
-                        } else {
-                            if (!this.autoComplete) this.$refs.input.select();
-                        }
-                        if (this.remote) {
-                            this.findChild(child => {
-                                child.selected = this.multiple ? this.model.indexOf(child.value) > -1 : this.model === child.value;
-                            });
-                            // remote下,设置了默认值,第一次打开时,搜索一次
-                            const options = this.$slots.default || [];
-                            if (this.query !== '' && !options.length) {
-                                this.remoteMethod(this.query);
-                            }
-                        }
-                    }
-                    this.broadcast('Drop', 'on-update-popper');
-                } else {
-                    if (this.filterable) {
-                        if (!this.autoComplete) this.$refs.input.blur();
-                        // #566 reset options visible
-                        setTimeout(() => {
-                            this.broadcastQuery('');
-                        }, 300);
+            query (query) {
+                this.$emit('on-query-change', query);
+                const {remoteMethod, lastRemoteQuery} = this;
+                const hasValidQuery = query !== '' && (query !== lastRemoteQuery || !lastRemoteQuery);
+                const shouldCallRemoteMethod = remoteMethod && hasValidQuery;
+
+                if (shouldCallRemoteMethod){
+                    this.focusIndex = -1;
+                    const promise = this.remoteMethod(query);
+                    this.initialLabel = '';
+                    if (promise && promise.then){
+                        promise.then(options => {
+                            if (options) this.options = options;
+                        });
                     }
-                    this.broadcast('Drop', 'on-destroy-popper');
                 }
+                if (query !== '' && this.remote) this.lastRemoteQuery = query;
             },
-            query (val) {
-                if (this.remote && this.remoteMethod) {
-                    if (!this.selectToChangeQuery) {
-                        this.$emit('on-query-change', val);
-                        this.remoteMethod(val);
-                    }
-                    this.focusIndex = 0;
-                    this.findChild(child => {
-                        child.isFocus = false;
-                    });
-                } else {
-                    if (!this.selectToChangeQuery) {
-                        this.$emit('on-query-change', val);
-                    }
-                    this.broadcastQuery(val);
+            loading(state){
+                if (state === false){
+                    this.updateSlotOptions();
+                }
+            },
+            isFocused(focused){
+                const {selectHead, reference} = this.$refs;
+                const el = this.filterable ? selectHead.$el.querySelector('input') : reference;
+                el[this.isFocused ? 'focus' : 'blur']();
 
-                    let is_hidden = true;
+                // restore query value in filterable single selects
+                const [selectedOption] = this.values;
+                if (selectedOption && this.filterable && !this.multiple && !focused){
+                    const selectedLabel = selectedOption.label || selectedOption.value;
+                    if (this.query !== selectedLabel) this.query = selectedLabel;
+                }
+            },
+            focusIndex(index){
+                if (index < 0) return;
+                // update scroll
+                const optionValue = this.flatOptions[index].componentOptions.propsData.value;
+                const optionInstance = findChild(this, ({$options}) => {
+                    return $options.componentName === 'select-item' && $options.propsData.value === optionValue;
+                });
 
-                    this.$nextTick(() => {
-                        this.findChild((child) => {
-                            if (!child.hidden) {
-                                is_hidden = false;
-                            }
-                        });
-                        this.notFound = is_hidden;
-                    });
+                let bottomOverflowDistance = optionInstance.$el.getBoundingClientRect().bottom - this.$refs.dropdown.$el.getBoundingClientRect().bottom;
+                let topOverflowDistance = optionInstance.$el.getBoundingClientRect().top - this.$refs.dropdown.$el.getBoundingClientRect().top;
+                if (bottomOverflowDistance > 0) {
+                    this.$refs.dropdown.$el.scrollTop += bottomOverflowDistance;
+                }
+                if (topOverflowDistance < 0) {
+                    this.$refs.dropdown.$el.scrollTop += topOverflowDistance;
                 }
-                this.selectToChangeQuery = false;
-                this.broadcast('Drop', 'on-update-popper');
             }
         }
     };
diff --git a/src/styles/components/select.less b/src/styles/components/select.less
index adb6c6e..046501f 100644
--- a/src/styles/components/select.less
+++ b/src/styles/components/select.less
@@ -25,23 +25,14 @@
         border: 1px solid @border-color-base;
         transition: all @transition-time @ease-in-out;
 
-        .@{select-prefix-cls}-arrow:nth-of-type(1) {
-            display: none;
-            cursor: pointer;
-        }
-
-        &:hover {
+        &:hover, &-focused {
             .hover();
-            .@{select-prefix-cls}-arrow:nth-of-type(1) {
+            .@{select-prefix-cls}-arrow {
                 display: inline-block;
             }
         }
     }
 
-    &-show-clear &-selection:hover .@{select-prefix-cls}-arrow:nth-of-type(2){
-        display: none;
-    }
-
     &-arrow {
         .inner-arrow();
     }
@@ -51,14 +42,9 @@
             .active();
         }
 
-        .@{select-prefix-cls}-arrow:nth-of-type(2) {
+        .@{select-prefix-cls}-arrow {
             transform: rotate(180deg);
-        }
-    }
-    &:focus{
-        outline: 0;
-        .@{select-prefix-cls}-selection{
-            .active();
+            display: inline-block;
         }
     }
 
@@ -66,7 +52,7 @@
         .@{select-prefix-cls}-selection {
             .disabled();
 
-            .@{select-prefix-cls}-arrow:nth-of-type(1) {
+            .@{select-prefix-cls}-arrow {
                 display: none;
             }
 
@@ -74,7 +60,7 @@
                 border-color: @border-color-base;
                 box-shadow: none;
 
-                .@{select-prefix-cls}-arrow:nth-of-type(2) {
+                .@{select-prefix-cls}-arrow {
                     display: inline-block;
                 }
             }
--
libgit2 0.21.4