WeChat Mini Program Select Dropdown Component

1. Source Code

https://github.com/imxiaoer/WeChatMiniSelect

2. Preview

Screenshot

Note: The screen recording quality is poor, so you might see ghosting artiafcts.

3. Component Code

3.1 select.wxml

<view class="select-box">
  <view class="select-current" catchtap="toggleDropdown">
    <text class="current-label">{{current.name}}</text>
  </view>
  <view class="option-list" wx:if="{{isOpen}}" catchtap="onOptionTap">
    <text class="option"
      data-id="{{defaultOption.id}}"
      data-name="{{defaultOption.name}}">
      {{defaultOption.name}}
    </text>
    <text class="option"
      wx:for="{{transformedOptions}}"
      wx:key="{{item.id}}"
      data-id="{{item.id}}"
      data-name="{{item.name}}">
      {{item.name}}
    </text>
  </view>
</view>

Explanation: Using catchtap instead of bindtap prevents event bubbling. To close the dropdown when tapping outside, the parent page (index.wxml) binds a bindtap="close" handler on its outermost view. If bubbling were not stopped, the parent's close method would execute incorrectly.

3.2 select.js

Component({
  properties: {
    options: {
      type: Array,
      value: []
    },
    defaultOption: {
      type: Object,
      value: {
        id: '000',
        name: 'All Cities'
      }
    },
    keyField: {
      type: String,
      value: 'id'
    },
    textField: {
      type: String,
      value: 'name'
    }
  },
  data: {
    transformedOptions: [],
    isOpen: false,
    current: {}
  },
  methods: {
    onOptionTap(e) {
      const dataset = e.target.dataset;
      this.setData({
        current: dataset,
        isOpen: false
      });

      // Emit event to parent component with selected data
      this.triggerEvent("change", { ...dataset });
    },
    toggleDropdown() {
      this.setData({
        isOpen: !this.data.isOpen
      });
    },

    // Method to be called from parent to close the dropdown
    closeDropdown() {
      this.setData({
        isOpen: false
      });
    }
  },
  lifetimes: {
    attached() {
      // Normalize properties: convert custom keys to internal 'id' and 'name' keys
      let transformed = [];
      if (this.data.keyField !== 'id' || this.data.textField !== 'name') {
        for (let item of this.data.options) {
          let { [this.data.keyField]: id, [this.data.textField]: name } = item;
          transformed.push({ id, name });
        }
      } else {
        transformed = this.data.options;
      }
      this.setData({
        current: Object.assign({}, this.data.defaultOption),
        transformedOptions: transformed
      });
    }
  }
});

Explanation: The keyField and textField properties allow attribute name conversion. For example, if the original data is:

[
  { city_id: '001', city_name: 'Beijing' },
  { city_id: '002', city_name: 'Shanghai' },
  { city_id: '003', city_name: 'Shenzhen' }
]

But the component expects:

[
  { id: '001', name: 'Beijing' },
  { id: '002', name: 'Shanghai' },
  { id: '003', name: 'Shenzhen' }
]

Thus, we map city_id to id and city_name to name using keyField and textField.

3.3 select.json

{
  "component": true,
  "usingComponents": {}
}

3.4 select.wxss

.select-box {
  position: relative;
  width: 100%;
  font-size: 30rpx;
}

.select-current {
  position: relative;
  width: 100%;
  padding: 0 10rpx;
  line-height: 70rpx;
  border: 1rpx solid #ddd;
  border-radius: 6rpx;
  box-sizing: border-box;
}

.select-current::after {
  position: absolute;
  display: block;
  right: 16rpx;
  top: 30rpx;
  content: '';
  width: 0;
  height: 0;
  border: 10rpx solid transparent;
  border-top: 10rpx solid #999;
}

.current-label {
  display: block;
  width: 85%;
  height: 100%;
  word-wrap: normal;
  overflow: hidden;
}

.option-list {
  position: absolute;
  left: 0;
  top: 76rpx;
  width: 100%;
  padding: 12rpx 20rpx 10rpx 20rpx;
  border-radius: 6rpx;
  box-sizing: border-box;
  z-index: 99;
  box-shadow: 0rpx 0rpx 1rpx 1rpx rgba(0, 0, 0, 0.2) inset;
  background-color: #fff;
}

.option {
  display: block;
  width: 100%;
  line-height: 70rpx;
  border-bottom: 1rpx solid #eee;
}

.option:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

4. Usage

index.wxml

<view class="container" bindtap="closeDropdown">
  <view class="select-wrapper">
    <select 
      id="citySelect" 
      options="{{cityData}}" 
      keyField="city_id" 
      textField="city_name" 
      bind:change="onCityChange"
    />
  </view>
</view>

Note: The parent page's closeDropdown method will close the select when tapping outside the dropodwn.

Tags: WeChat Mini Program dropdown Component SELECT frontend

Posted on Fri, 04 Sep 2026 16:40:21 +0000 by misty