Support dark mode.
diff --git a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
index e006b27..3502f78 100644
--- a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
+++ b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
@@ -618,6 +618,12 @@
 		D735F1B222D761F800B53CDF /* log_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D735F1AC22D761F800B53CDF /* log_utils.cpp */; };
 		D735F1B322D761F800B53CDF /* log_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D735F1AC22D761F800B53CDF /* log_utils.cpp */; };
 		D77286FF22C9B22C00E1DA7D /* eagle_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD9205FA223651D800EDF93D /* eagle_bridge.cpp */; };
+		D7C96CF3237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		D7C96CF4237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		D7C96CF7237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */; };
+		D7C96CF8237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */; };
+		D7C96CF9237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */; };
+		D7C96CFA237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */; };
 		DC03ADB91D508719003F76E7 /* WXTextAreaComponent.mm in Sources */ = {isa = PBXBuildFile; fileRef = DC03ADB71D508719003F76E7 /* WXTextAreaComponent.mm */; };
 		DC03ADBA1D508719003F76E7 /* WXTextAreaComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = DC03ADB81D508719003F76E7 /* WXTextAreaComponent.h */; };
 		DC15A3DB2010BC93009C8977 /* weex-main-jsfm.js in Resources */ = {isa = PBXBuildFile; fileRef = DC15A3D92010BC93009C8977 /* weex-main-jsfm.js */; };
@@ -1351,6 +1357,9 @@
 		D3FC0DF61C508B2A002B9E31 /* WXTimerModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXTimerModule.m; sourceTree = "<group>"; };
 		D735F1AB22D761F800B53CDF /* log_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log_utils.h; sourceTree = "<group>"; };
 		D735F1AC22D761F800B53CDF /* log_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = log_utils.cpp; sourceTree = "<group>"; };
+		D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXDarkThemeProtocol.h; sourceTree = "<group>"; };
+		D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXDarkThemeDefaultImpl.h; sourceTree = "<group>"; };
+		D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXDarkThemeDefaultImpl.m; sourceTree = "<group>"; };
 		DAB176F008F516E4F9391C61 /* libPods-WeexSDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WeexSDK.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		DC03ADB71D508719003F76E7 /* WXTextAreaComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WXTextAreaComponent.mm; sourceTree = "<group>"; };
 		DC03ADB81D508719003F76E7 /* WXTextAreaComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXTextAreaComponent.h; sourceTree = "<group>"; };
@@ -1476,6 +1485,8 @@
 		59A583031CF5B2FD0081FD3E /* Handler */ = {
 			isa = PBXGroup;
 			children = (
+				D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */,
+				D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */,
 				33CE190C2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.h */,
 				33CE190D2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.m */,
 				59A583041CF5B2FD0081FD3E /* WXNavigationDefaultImpl.h */,
@@ -1857,6 +1868,7 @@
 		77D1611C1C02DD3C0010B15B /* Protocol */ = {
 			isa = PBXGroup;
 			children = (
+				D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */,
 				33CE19122153444900CF9670 /* WXJSFrameworkLoadProtocol.h */,
 				17036A5220FDE7490029AE3D /* WXApmProtocol.h */,
 				17C74F0E2072147A00AB4CAB /* WXAnalyzerProtocol.h */,
@@ -2410,6 +2422,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D7C96CF3237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */,
 				F75C591C2313C1FC002FFF94 /* WXStreamModule.h in Headers */,
 				74A4BA9E1CB3C0A100195969 /* WXHandlerFactory.h in Headers */,
 				59A583081CF5B2FD0081FD3E /* WXNavigationDefaultImpl.h in Headers */,
@@ -2497,6 +2510,7 @@
 				59CE27E81CC387DB000BE37A /* WXEmbedComponent.h in Headers */,
 				B8D66C0F21255730003960BD /* core_side_in_platform.h in Headers */,
 				B8D66C9121255730003960BD /* render_factory_interface.h in Headers */,
+				D7C96CF7237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */,
 				DCE2CF9B1F46D4220021BDC4 /* WXVoiceOverModule.h in Headers */,
 				453F3756219A76CA00A03F1D /* default_request_handler.h in Headers */,
 				74BB5FB91DFEE81A004FC3DF /* WXMetaModule.h in Headers */,
@@ -2656,6 +2670,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D7C96CF4237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */,
 				DCA4461D1EFA5AAA00D0CFA8 /* WXHandlerFactory.h in Headers */,
 				DCA446101EFA5A8500D0CFA8 /* WXBridgeMethod.h in Headers */,
 				DCA4461A1EFA5AA000D0CFA8 /* WXInvocationConfig.h in Headers */,
@@ -2724,6 +2739,7 @@
 				DCA445A21EFA570100D0CFA8 /* WXScrollerComponent.h in Headers */,
 				B8D66C7621255730003960BD /* render_object.h in Headers */,
 				DCA445B71EFA579200D0CFA8 /* WXImgLoaderProtocol.h in Headers */,
+				D7C96CF8237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */,
 				1746EA7420E9D253007E55BD /* WXComponent_performance.h in Headers */,
 				B8D66CEB21255B2A003960BD /* WXWebSocketLoader.h in Headers */,
 				DCA445C21EFA57D700D0CFA8 /* WXBaseViewController.h in Headers */,
@@ -3324,6 +3340,7 @@
 				749DC27C1D40827B009E1C91 /* WXMonitor.m in Sources */,
 				C4B834271DE69B09007AD27E /* WXPickerModule.m in Sources */,
 				745B2D691E5A8E1E0092D38A /* WXMultiColumnLayout.m in Sources */,
+				D7C96CF9237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */,
 				77788B752229252D000D5102 /* render_page_custom.cpp in Sources */,
 				77D161391C02DE940010B15B /* WXBridgeManager.m in Sources */,
 				74BF19F91F5139BB00AEE3D7 /* WXJSASTParser.mm in Sources */,
@@ -3515,6 +3532,7 @@
 				DCA445981EFA55B300D0CFA8 /* WXSDKInstance.m in Sources */,
 				DCA445991EFA55B300D0CFA8 /* WXJSExceptionInfo.m in Sources */,
 				DCA4459A1EFA55B300D0CFA8 /* WXResourceRequest.m in Sources */,
+				D7C96CFA237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */,
 				77788B762229252D000D5102 /* render_page_custom.cpp in Sources */,
 				DCA4459B1EFA55B300D0CFA8 /* WXResourceRequestHandlerDefaultImpl.m in Sources */,
 				DCA4459C1EFA55B300D0CFA8 /* WXResourceResponse.m in Sources */,
diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
index cfea370..2d26ff0 100644
--- a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
@@ -760,7 +760,7 @@
 
 - (WXJSExpression *)parsePrimaryExpression
 {
-    int type = _lookahead->type;
+    WXJSTokenType type = _lookahead->type;
     
     if (type == WXJSTokenTypePunctuator) {
         if (_lookahead->value == "[") {
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
index a97dfcb..6dff036 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
@@ -50,7 +50,9 @@
      *  View
      */
     UIColor *_styleBackgroundColor;
+    UIColor *_darkThemeBackgroundColor;
     NSString *_backgroundImage;
+    NSString *_darkThemeBackgroundImage;
     NSString *_clipRadius;
     WXClipType _clipToBounds;
     UIView *_view;
@@ -115,9 +117,13 @@
     WXThreadSafeCounter *_displayCounter;
     
     UIColor *_borderTopColor;
+    UIColor *_darkThemeBorderTopColor;
     UIColor *_borderRightColor;
+    UIColor *_darkThemeBorderRightColor;
     UIColor *_borderLeftColor;
+    UIColor *_darkThemeBorderLeftColor;
     UIColor *_borderBottomColor;
+    UIColor *_darkThemeBorderBottomColor;
     
     CGFloat _borderTopWidth;
     CGFloat _borderRightWidth;
@@ -179,6 +185,7 @@
  DO NOT use "_backgroundColor" directly. The same reason as '_transform'.
  */
 @property (atomic, strong) UIColor* styleBackgroundColor;
+@property (atomic, strong) UIColor* darkThemeBackgroundColor;
 
 ///--------------------------------------
 /// @name Package Internal Methods
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m
index 1cb8245..2a83fee 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m
+++ b/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m
@@ -53,14 +53,14 @@
 
 @interface WXImageComponent ()
 {
-    NSString * _imageSrc;
-    pthread_mutex_t _imageSrcMutex;
-    pthread_mutexattr_t _propertMutexAttr;
     BOOL _shouldUpdateImage;
     BOOL _mainImageSuccess;
 }
 
+@property (atomic, strong) NSString *src;
+@property (atomic, strong) NSString *darkThemeSrc;
 @property (atomic, strong) NSString *placeholdSrc;
+@property (atomic, strong) NSString *darkThemePlaceholderSrc;
 @property (nonatomic, assign) CGFloat blurRadius;
 @property (nonatomic, assign) UIViewContentMode resizeMode;
 @property (nonatomic, assign) WXImageQuality imageQuality;
@@ -71,7 +71,7 @@
 @property (nonatomic) BOOL imageLoadEvent;
 @property (nonatomic) BOOL imageDownloadFinish;
 @property (nonatomic) BOOL downloadImageWithURL;
-@property (nonatomic ,strong) NSString* preUrl;
+@property (nonatomic, strong) NSString* preUrl;
 
 @end
 
@@ -88,17 +88,15 @@
             WXImageUpdateQueue = dispatch_queue_create("com.taobao.weex.ImageUpdateQueue", DISPATCH_QUEUE_SERIAL);
         }
         
-        pthread_mutexattr_init(&(_propertMutexAttr));
-        pthread_mutexattr_settype(&(_propertMutexAttr), PTHREAD_MUTEX_RECURSIVE);
-        pthread_mutex_init(&(_imageSrcMutex), &(_propertMutexAttr));
-        
         if (attributes[@"src"]) {
-             pthread_mutex_lock(&(_imageSrcMutex));
-            _imageSrc = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
-             pthread_mutex_unlock(&(_imageSrcMutex));
+            self.src = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
         } else {
             WXLogWarning(@"image src is nil");
         }
+        if (attributes[@"darkThemeSrc"]) {
+            self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+        }
+        
         [self configPlaceHolder:attributes];
         
         NSString *resizeMode = attributes[@"resize"];
@@ -135,6 +133,9 @@
     if (attributes[@"placeHolder"] || attributes[@"placeholder"]) {
         self.placeholdSrc = [[WXConvert NSString:attributes[@"placeHolder"]?:attributes[@"placeholder"]]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
     }
+    if (attributes[@"darkThemePlaceholder"]) {
+        self.darkThemePlaceholderSrc = [[WXConvert NSString:attributes[@"darkThemePlaceholder"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+    }
 }
 
 - (void)configFilter:(NSDictionary *)styles needUpdate:(BOOL)needUpdate
@@ -258,12 +259,13 @@
 - (void)dealloc
 {
     [self cancelImage];
-    pthread_mutex_destroy(&(_imageSrcMutex));
-    pthread_mutexattr_destroy(&_propertMutexAttr);
 }
 
 - (void)updateAttributes:(NSDictionary *)attributes
 {
+    if (attributes[@"darkThemeSrc"]) {
+        self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+    }
     if (attributes[@"src"]) {
         [self setImageSrc:[[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
     }
@@ -293,7 +295,16 @@
     [self _clipsToBounds];
     
     [self updateImage];
-    
+}
+
+- (void)themeDidChange:(NSString*)theme
+{
+    [super themeDidChange:theme];
+    if (_view) {
+        if (self.darkThemeSrc || self.darkThemePlaceholderSrc) {
+            [self updateImage];
+        }
+    }
 }
 
 - (BOOL)_needsDrawBorder
@@ -358,26 +369,15 @@
     }
 }
 
-- (NSString *)imageSrc
-{
-    pthread_mutex_lock(&(_imageSrcMutex));
-    NSString * imageSrcCpy = [_imageSrc copy];
-    pthread_mutex_unlock(&(_imageSrcMutex));
-    
-    return imageSrcCpy;
-}
-
 - (void)setImageSrc:(NSString*)src
 {
-    if ([src isEqualToString:_imageSrc]) {
+    if ([src isEqualToString:self.src]) {
         // if image src is equal to then ignore it.
         return;
     }
-    pthread_mutex_lock(&(_imageSrcMutex));
-    _imageSrc = src;
+    self.src = src;
     _imageDownloadFinish = NO;
     ((UIImageView*)self.view).image = nil;
-    pthread_mutex_unlock(&(_imageSrcMutex));
     
     [self updateImage];
 }
@@ -397,14 +397,19 @@
         _shouldUpdateImage = YES;
         return;
     }
+    
+    BOOL isDarkMode = [self.weexInstance isDarkTheme];
+    NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src;
+    NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc;
+    
     __weak typeof(self) weakSelf = self;
     if (_downloadImageWithURL && [[self imageLoader] respondsToSelector:@selector(setImageViewWithURL:url:placeholderImage:options:progress:completed:)]) {
         _mainImageSuccess = NO;
         
         NSString *newURL = nil;
-        if (self.placeholdSrc) {
-            newURL = [self.placeholdSrc copy];
-            WX_REWRITE_URL([self placeholdSrc], WXResourceTypeImage, self.weexInstance)
+        if (choosedPlaceholder) {
+            newURL = [choosedPlaceholder copy];
+            WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, self.weexInstance)
             NSDictionary* extInfo = @{@"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
             [[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:extInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
                 /* We cannot rely on image library even if we call setImage with placeholer before calling setImage with real url.
@@ -414,8 +419,8 @@
                     UIImageView *imageView = (UIImageView *)strongSelf.view;
                     if (imageView && imageView.image == image && strongSelf->_mainImageSuccess) {
                         // reload image with main image url
-                        NSString* newURL = [[strongSelf imageSrc] copy];
-                        WX_REWRITE_URL([strongSelf imageSrc], WXResourceTypeImage, strongSelf.weexInstance)
+                        NSString* newURL = [choosedSrc copy];
+                        WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, strongSelf.weexInstance)
                         NSDictionary *userInfo = @{@"imageQuality":@(strongSelf.imageQuality), @"imageSharp":@(strongSelf.imageSharp),  @"blurRadius":@(strongSelf.blurRadius), @"instanceId":[strongSelf _safeInstanceId], @"pageURL": strongSelf.weexInstance.scriptURL ?: @""};
                         [[strongSelf imageLoader] setImageViewWithURL:imageView url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
                             WXLogInfo(@"Image re-requested because placeholder may override main image. %@", imageURL);
@@ -424,11 +429,11 @@
                 }
             }];
         }
-        newURL = [[self imageSrc] copy];
+        newURL = [choosedSrc copy];
         if ([newURL length] == 0) {
             return;
         }
-        WX_REWRITE_URL([self imageSrc], WXResourceTypeImage, self.weexInstance)
+        WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, self.weexInstance)
         NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp),  @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
         [[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:^(NSInteger receivedSize, NSInteger expectedSize) {
             // progress when loading image
@@ -444,9 +449,9 @@
                 WXLogError(@"Error downloading image: %@, detail:%@", imageURL.absoluteString, [error localizedDescription]);
                 
                 // retry set placeholder, maybe placeholer image can be downloaded
-                if (strongSelf.placeholdSrc) {
-                    NSString *newURL = [strongSelf.placeholdSrc copy];
-                    WX_REWRITE_URL([strongSelf placeholdSrc], WXResourceTypeImage, strongSelf.weexInstance)
+                if (choosedPlaceholder) {
+                    NSString *newURL = [choosedPlaceholder copy];
+                    WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, strongSelf.weexInstance)
                     [[strongSelf imageLoader] setImageViewWithURL:(UIImageView*)strongSelf.view
                                                               url:[NSURL URLWithString:newURL]
                                                  placeholderImage:nil
@@ -513,7 +518,7 @@
             [strongSelf updatePlaceHolderWithFailedBlock:downloadFailed];
             [strongSelf updateContentImageWithFailedBlock:downloadFailed];
 
-            if (!strongSelf.imageSrc && !strongSelf.placeholdSrc) {
+            if (!choosedSrc && !choosedPlaceholder) {
                 dispatch_async(dispatch_get_main_queue(), ^{
                     strongSelf.layer.contents = nil;
                     strongSelf.imageDownloadFinish = YES;
@@ -526,15 +531,16 @@
 
 - (void)updatePlaceHolderWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock
 {
-    NSString *placeholderSrc = self.placeholdSrc;
+    BOOL isDarkMode = [self.weexInstance isDarkTheme];
+    NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc;
     
-    if ([WXUtility isBlankString:placeholderSrc]) {
+    if ([WXUtility isBlankString:choosedPlaceholder]) {
         return;
     }
     
-    WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, placeholderSrc);
-    NSString *newURL = [self.placeholdSrc copy];
-    WX_REWRITE_URL(self.placeholdSrc, WXResourceTypeImage, self.weexInstance)
+    WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, choosedPlaceholder);
+    NSString *newURL = [choosedPlaceholder copy];
+    WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, self.weexInstance)
     
     __weak typeof(self) weakSelf = self;
     self.placeholderOperation = [[self imageLoader] downloadImageWithURL:newURL imageFrame:self.calculatedFrame
@@ -543,16 +549,21 @@
     {
         dispatch_async(dispatch_get_main_queue(), ^{
             __strong typeof(self) strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
             UIImage *viewImage = ((UIImageView *)strongSelf.view).image;
             if (error) {
-                downloadFailedBlock(placeholderSrc,error);
+                downloadFailedBlock(choosedPlaceholder, error);
                 if ([strongSelf isViewLoaded] && !viewImage) {
                     ((UIImageView *)(strongSelf.view)).image = nil;
                     [strongSelf readyToRender];
                 }
                 return;
             }
-            if (![placeholderSrc isEqualToString:strongSelf.placeholdSrc]) {
+            
+            NSString* currentPlaceholder = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemePlaceholderSrc ?: strongSelf.placeholdSrc) : strongSelf.placeholdSrc;
+            if (![choosedPlaceholder isEqualToString:currentPlaceholder]) {
                 return;
             }
             
@@ -570,20 +581,25 @@
 
 - (void)updateContentImageWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock
 {
-    NSString *imageSrc = [NSString stringWithFormat:@"%@", self.imageSrc?:@""];
-    if ([WXUtility isBlankString:imageSrc]) {
+    BOOL isDarkMode = [self.weexInstance isDarkTheme];
+    NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src;
+    
+    if ([WXUtility isBlankString:choosedSrc]) {
         WXLogError(@"image src is empty");
         return;
     }
     
-    WXLogDebug(@"Updating image:%@, component:%@", self.imageSrc, self.ref);
+    WXLogDebug(@"Updating image:%@, component:%@", choosedSrc, self.ref);
     NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
-    NSString * newURL = [imageSrc copy];
-    WX_REWRITE_URL(imageSrc, WXResourceTypeImage, self.weexInstance)
+    NSString * newURL = [choosedSrc copy];
+    WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, self.weexInstance)
     __weak typeof(self) weakSelf = self;
     weakSelf.imageOperation = [[weakSelf imageLoader] downloadImageWithURL:newURL imageFrame:weakSelf.calculatedFrame userInfo:userInfo completed:^(UIImage *image, NSError *error, BOOL finished) {
         dispatch_async(dispatch_get_main_queue(), ^{
             __strong typeof(self) strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
             
             if (strongSelf.imageLoadEvent) {
                 NSMutableDictionary *sizeDict = [NSMutableDictionary new];
@@ -598,12 +614,13 @@
                 [strongSelf fireEvent:@"load" params:@{ @"success": error? @false : @true,@"size":sizeDict}];
             }
             if (error) {
-                downloadFailedBlock(imageSrc, error);
+                downloadFailedBlock(choosedSrc, error);
                 [strongSelf readyToRender];
                 return ;
             }
             
-            if (![imageSrc isEqualToString:strongSelf.imageSrc]) {
+            NSString* currentSrc = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemeSrc ?: strongSelf.src) : strongSelf.src;
+            if (![choosedSrc isEqualToString:currentSrc]) {
                 return ;
             }
             
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
index 8ffeaf3..8e30d76 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
@@ -27,6 +27,7 @@
 #import "WXImgLoaderProtocol.h"
 #import "WXComponentManager.h"
 #import "WXLog.h"
+#import "WXDarkThemeProtocol.h"
 #include <pthread/pthread.h>
 
 @interface WXRichNode : NSObject
@@ -35,7 +36,9 @@
 @property (nonatomic, strong) NSString  *ref;
 @property (nonatomic, strong) NSString  *text;
 @property (nonatomic, strong) UIColor   *color;
+@property (nonatomic, strong) UIColor   *darkThemeColor;
 @property (nonatomic, strong) UIColor   *backgroundColor;
+@property (nonatomic, strong) UIColor   *darkThemeBackgroundColor;
 @property (nonatomic, strong) NSString  *fontFamily;
 @property (nonatomic, assign) CGFloat   fontSize;
 @property (nonatomic, assign) CGFloat   fontWeight;
@@ -91,7 +94,9 @@
     id value = styles[@#key]; \
     if (value) { \
         node.key = [WXConvert type:value];\
-    } else if (!([@#key isEqualToString:@"backgroundColor"] || [@#key isEqualToString:@"textDecoration"]) && superNode.key ) { \
+    } else if (!([@#key isEqualToString:@"backgroundColor"] || \
+        [@#key isEqualToString:@"darkThemeBackgroundColor"] || \
+        [@#key isEqualToString:@"textDecoration"]) && superNode.key ) { \
         node.key = superNode.key; \
     } \
 } while(0);
@@ -190,7 +195,9 @@
 - (void)fillCSSStyles:(NSDictionary *)styles toNode:(WXRichNode *)node superNode:(WXRichNode *)superNode
 {
     WX_STYLE_FILL_RICHTEXT(color, UIColor)
+    WX_STYLE_FILL_RICHTEXT(darkThemeColor, UIColor)
     WX_STYLE_FILL_RICHTEXT(backgroundColor, UIColor)
+    WX_STYLE_FILL_RICHTEXT(darkThemeBackgroundColor, UIColor)
     WX_STYLE_FILL_RICHTEXT(fontFamily, NSString)
     WX_STYLE_FILL_RICHTEXT_PIXEL(fontSize)
     WX_STYLE_FILL_RICHTEXT(fontWeight, WXTextWeight)
@@ -421,6 +428,15 @@
     };
 }
 
+- (void)themeDidChange:(NSString*)theme
+{
+    [super themeDidChange:theme];
+    if ([self isViewLoaded]) {
+        // Force inner layout
+        [self innerLayout];
+    }
+}
+
 #pragma mark Text Building
 
 - (NSMutableAttributedString *)buildAttributeString
@@ -434,6 +450,16 @@
     NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] init];
     NSUInteger location;
     
+    BOOL invert = self.invertForDarkTheme;
+    
+    // Invert default background color.
+    UIColor* defaultTextColor = [UIColor blackColor];
+    UIColor* defaultBackgroundColor = _backgroundColor;
+    if (invert && [self.weexInstance isDarkTheme]) {
+        defaultTextColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:[UIColor blackColor] ofScene:[self colorSceneType] withDefault:[UIColor blackColor]];
+        defaultBackgroundColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:_backgroundColor ofScene:[self colorSceneType] withDefault:_backgroundColor];
+    }
+    
     __weak typeof(self) weakSelf = self;
     for (WXRichNode *node in array) {
         location = attrStr.length;
@@ -444,8 +470,10 @@
                 [attrStr.mutableString appendString:text];
                 
                 NSRange range = NSMakeRange(location, text.length);
-                [attrStr addAttribute:NSForegroundColorAttributeName value:node.color ?: [UIColor blackColor] range:range];
-                [attrStr addAttribute:NSBackgroundColorAttributeName value:node.backgroundColor ?: _backgroundColor range:range];
+                UIColor* textColor = [self.weexInstance chooseColor:node.color darkThemeColor:node.darkThemeColor invert:invert scene:[self colorSceneType]];
+                UIColor* bgColor = [self.weexInstance chooseColor:node.backgroundColor darkThemeColor:node.darkThemeBackgroundColor invert:invert scene:[self colorSceneType]];
+                [attrStr addAttribute:NSForegroundColorAttributeName value:textColor ?: defaultTextColor range:range];
+                [attrStr addAttribute:NSBackgroundColorAttributeName value:bgColor ?: defaultBackgroundColor range:range];
                 
                 UIFont *font = [WXUtility fontWithSize:node.fontSize textWeight:node.fontWeight textStyle:WXTextStyleNormal fontFamily:node.fontFamily scaleFactor:self.weexInstance.pixelScaleFactor];
                 if (font) {
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
index 3a95fac..3bb7948 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
@@ -120,6 +120,8 @@
 
 @interface WXTextComponent()
 @property (atomic, strong) NSString *fontFamily;
+@property (atomic, strong) UIColor *textColor;
+@property (atomic, strong) UIColor *darkThemeTextColor;
 @end
 
 @implementation WXTextComponent
@@ -128,7 +130,6 @@
     UIEdgeInsets _padding;
     NSTextStorage *_textStorage;
     float _textStorageWidth;
-    float _color[4];
     float _fontSize;
     float _fontWeight;
     WXTextStyle _fontStyle;
@@ -174,8 +175,6 @@
         } else {
             _useCoreText = YES;
         }
-        
-        _color[0] = -1.0;
 
         [self fillCSSStyles:styles];
         [self fillAttributes:attributes];
@@ -261,25 +260,41 @@
     WX_STYLE_FILL_TEXT_PIXEL(letterSpacing, letterSpacing, YES) //!OCLint
     WX_STYLE_FILL_TEXT(wordWrap, wordWrap, NSString, YES); //!OCLint
 
-    UIColor* color = nil;
-    id value = styles[@"color"];
-    if (value) {
-        if([WXUtility isBlankString:value]){
-            color = [UIColor blackColor];
-        } else {
-            color = [WXConvert UIColor:value];
+    do {
+        UIColor* color = nil;
+        id value = styles[@"color"];
+        if (value) {
+            if([WXUtility isBlankString:value]){
+                color = [UIColor blackColor];
+            } else {
+                color = [WXConvert UIColor:value];
+            }
+            if (color) {
+                self.textColor = color;
+                [self setNeedsRepaint];
+            }
         }
-        if (color) {
-            [self setNeedsRepaint];
-            CGFloat red, green, blue, alpha;
-            [color getRed:&red green:&green blue:&blue alpha:&alpha];
-            _color[0] = red;
-            _color[1] = green;
-            _color[2] = blue;
-            _color[3] = alpha;
+        if (self.textColor == nil) {
+            self.textColor = [UIColor blackColor];
         }
-    }
+    } while (0);
 
+    do {
+        UIColor* color = nil;
+        id value = styles[@"darkThemeColor"];
+        if (value) {
+            if([WXUtility isBlankString:value]){
+                color = [UIColor blackColor];
+            } else {
+                color = [WXConvert UIColor:value];
+            }
+            if (color) {
+                self.darkThemeTextColor = color;
+                [self setNeedsRepaint];
+            }
+        }
+    } while (0);
+    
     if (self.fontFamily && !_observerIconfont) {
         // notification received when custom icon font file download finish
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repaintText:) name:WX_ICONFONT_DOWNLOAD_NOTIFICATION object:nil];
@@ -371,6 +386,20 @@
     [self setNeedsRepaint];
 }
 
+- (void)themeDidChange:(NSString*)theme
+{
+    [self setNeedsRepaint];
+    [super themeDidChange:theme];
+    if (_view) {
+        [self setNeedsDisplay];
+    }
+}
+
+- (WXColorScene)colorSceneType
+{
+    return WXColorSceneText;
+}
+
 - (BOOL)needsDrawRect
 {
     return YES;
@@ -497,8 +526,9 @@
         string = @"";
     }
     NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString: string];
-    if (_color[0] >= 0) {
-        [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:_color[0] green:_color[1] blue:_color[2] alpha:_color[3]] range:NSMakeRange(0, string.length)];
+    UIColor* textColor = [self.weexInstance chooseColor:self.textColor darkThemeColor:self.darkThemeTextColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    if (textColor) {
+        [attributedString addAttribute:NSForegroundColorAttributeName value:textColor range:NSMakeRange(0, string.length)];
     }
     
     // set font
@@ -585,8 +615,9 @@
     NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
     
     // set textColor
-    if (_color[0] >= 0) {
-        [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:_color[0] green:_color[1] blue:_color[2] alpha:_color[3]] range:NSMakeRange(0, string.length)];
+    UIColor* textColor = [self.weexInstance chooseColor:self.textColor darkThemeColor:self.darkThemeTextColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    if (textColor) {
+        [attributedString addAttribute:NSForegroundColorAttributeName value:textColor range:NSMakeRange(0, string.length)];
     }
     
     // set font
@@ -1112,10 +1143,11 @@
 {
     [super _resetCSSNodeStyles:styles];
     if ([styles containsObject:@"color"]) {
-        _color[0] = 0;
-        _color[1] = 0;
-        _color[2] = 0;
-        _color[3] = 1.0;
+        self.textColor = [UIColor blackColor];
+        [self setNeedsRepaint];
+    }
+    if ([styles containsObject:@"darkThemeColor"]) {
+        self.darkThemeTextColor = nil;
         [self setNeedsRepaint];
     }
     if ([styles containsObject:@"fontSize"]) {
diff --git a/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m b/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m
index d209068..7b513e8 100644
--- a/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m
+++ b/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m
@@ -28,6 +28,8 @@
 #import "UIBezierPath+Weex.h"
 #import "WXRoundedRect.h"
 #import "WXSDKInstance.h"
+#import "WXDarkThemeProtocol.h"
+#import "WXHandlerFactory.h"
 
 #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
 
@@ -101,6 +103,29 @@
     WXAssertMainThread();
 }
 
+- (void)themeDidChange:(NSString*)theme
+{
+    WXAssertMainThread();
+    if (_view) {
+        if (![self _needsDrawBorder]) {
+            _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
+            _layer.backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
+        }
+        else {
+            [self setNeedsDisplay];
+        }
+        
+        if (_backgroundImage) {
+            [self setGradientLayer];
+        }
+    }
+}
+
+- (WXColorScene)colorSceneType
+{
+    return WXColorSceneUnknown;
+}
+
 #pragma mark Private
 
 - (WXDisplayBlock)_displayBlock
@@ -289,7 +314,7 @@
 - (void)_collectCompositingDisplayBlocks:(NSMutableArray *)displayBlocks context:(CGContextRef)context isCancelled:(BOOL(^)(void))isCancelled
 {
     // TODO: compositingChild has no chance to applyPropertiesToView, need update here?
-    UIColor *backgroundColor = self.styleBackgroundColor;
+    UIColor *backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
     BOOL clipsToBounds = _clipToBounds;
     CGRect frame = self.calculatedFrame;
     CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
@@ -349,8 +374,9 @@
     
     CGContextSetAlpha(context, _opacity);
     // fill background color
-    if (self.styleBackgroundColor && CGColorGetAlpha(self.styleBackgroundColor.CGColor) > 0) {
-        CGContextSetFillColorWithColor(context, self.styleBackgroundColor.CGColor);
+    UIColor* bgColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) {
+        CGContextSetFillColorWithColor(context, bgColor.CGColor);
         UIBezierPath *bezierPath = [UIBezierPath wx_bezierPathWithRoundedRect:rect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
         [bezierPath fill];
         WXPerformBlockOnMainThread(^{
@@ -367,7 +393,8 @@
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderTopWidth);
-        CGContextSetStrokeColorWithColor(context, _borderTopColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, size.width-topRight, topRight, topRight-_borderTopWidth/2, -M_PI_4+(_borderRightWidth>0?0:M_PI_4), -M_PI_2, 1);
         CGContextMoveToPoint(context, size.width-topRight, _borderTopWidth/2);
         CGContextAddLineToPoint(context, topLeft, _borderTopWidth/2);
@@ -388,7 +415,8 @@
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderLeftWidth);
-        CGContextSetStrokeColorWithColor(context, _borderLeftColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderLeftColor darkThemeColor:_darkThemeBorderLeftColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, topLeft, topLeft, topLeft-_borderLeftWidth/2, -M_PI, -M_PI_2-M_PI_4+(_borderTopWidth > 0?0:M_PI_4), 0);
         CGContextMoveToPoint(context, _borderLeftWidth/2, topLeft);
         CGContextAddLineToPoint(context, _borderLeftWidth/2, size.height-bottomLeft);
@@ -409,7 +437,8 @@
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderBottomWidth);
-        CGContextSetStrokeColorWithColor(context, _borderBottomColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderBottomColor darkThemeColor:_darkThemeBorderBottomColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, bottomLeft, size.height-bottomLeft, bottomLeft-_borderBottomWidth/2, M_PI-M_PI_4+(_borderLeftWidth>0?0:M_PI_4), M_PI_2, 1);
         CGContextMoveToPoint(context, bottomLeft, size.height-_borderBottomWidth/2);
         CGContextAddLineToPoint(context, size.width-bottomRight, size.height-_borderBottomWidth/2);
@@ -430,7 +459,8 @@
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderRightWidth);
-        CGContextSetStrokeColorWithColor(context, _borderRightColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderRightColor darkThemeColor:_darkThemeBorderRightColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, size.width-bottomRight, size.height-bottomRight, bottomRight-_borderRightWidth/2, M_PI_4+(_borderBottomWidth>0?0:M_PI_4), 0, 1);
         CGContextMoveToPoint(context, size.width-_borderRightWidth/2, size.height-bottomRight);
         CGContextAddLineToPoint(context, size.width-_borderRightWidth/2, topRight);
@@ -486,7 +516,17 @@
     if (!radiusEqual) {
         return YES;
     }
-    BOOL colorEqual = [_borderTopColor isEqual:_borderRightColor] && [_borderRightColor isEqual:_borderBottomColor] && [_borderBottomColor isEqual:_borderLeftColor];
+    
+    BOOL invert = self.invertForDarkTheme;
+    WXColorScene scene = [self colorSceneType];
+    UIColor* usingBorderTopColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:invert scene:scene];
+    UIColor* usingBorderRightColor = [self.weexInstance chooseColor:_borderRightColor darkThemeColor:_darkThemeBorderRightColor invert:invert scene:scene];
+    UIColor* usingBorderBottomColor = [self.weexInstance chooseColor:_borderBottomColor darkThemeColor:_darkThemeBorderBottomColor invert:invert scene:scene];
+    UIColor* usingBorderLeftColor = [self.weexInstance chooseColor:_borderLeftColor darkThemeColor:_darkThemeBorderLeftColor invert:invert scene:scene];
+    
+    BOOL colorEqual = [usingBorderTopColor isEqual:usingBorderRightColor] &&
+        [usingBorderRightColor isEqual:usingBorderBottomColor] &&
+        [usingBorderBottomColor isEqual:usingBorderLeftColor];
     if (!colorEqual) {
         return YES;
     }
@@ -579,6 +619,32 @@
     
     WX_CHECK_BORDER_PROP(Style, Top, Left, Bottom, Right, WXBorderStyle)
     WX_CHECK_BORDER_PROP(Color, Top, Left, Bottom, Right, UIColor)
+    do {
+        BOOL needsDisplay = NO;
+        if (styles[@"darkThemeBorderColor"]) {
+            _darkThemeBorderTopColor = _darkThemeBorderLeftColor = _darkThemeBorderRightColor = _darkThemeBorderBottomColor = [WXConvert UIColor:styles[@"darkThemeBorderColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderTopColor"]) {
+            _darkThemeBorderTopColor = [WXConvert UIColor:styles[@"darkThemeBorderTopColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderLeftColor"]) {
+            _darkThemeBorderLeftColor = [WXConvert UIColor:styles[@"darkThemeBorderLeftColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderRightColor"]) {
+            _darkThemeBorderRightColor = [WXConvert UIColor:styles[@"darkThemeBorderRightColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderBottomColor"]) {
+            _darkThemeBorderBottomColor = [WXConvert UIColor:styles[@"darkThemeBorderBottomColor"]];
+            needsDisplay = YES;
+        }
+        if (needsDisplay && updating) {
+            [self setNeedsDisplay];
+        }
+    } while (0);
     WX_CHECK_BORDER_PROP_PIXEL(Width, Top, Left, Bottom, Right)
     WX_CHECK_BORDER_PROP_PIXEL(Radius, TopLeft, TopRight, BottomLeft, BottomRight)
 
@@ -592,9 +658,9 @@
         } else if (!nowNeedsDrawBorder) {
             [self _resetNativeBorderRadius];
             _layer.borderWidth = _borderTopWidth;
-            _layer.borderColor = _borderTopColor.CGColor;
+            _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
             if ((_transition.transitionOptions & WXTransitionOptionsBackgroundColor) != WXTransitionOptionsBackgroundColor ) {
-                _layer.backgroundColor = self.styleBackgroundColor.CGColor;
+                _layer.backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
             }
         }
     }
@@ -606,7 +672,8 @@
     WXRoundedRect *borderRect = [[WXRoundedRect alloc] initWithRect:rect topLeft:_borderTopLeftRadius topRight:_borderTopRightRadius bottomLeft:_borderBottomLeftRadius bottomRight:_borderBottomRightRadius];
     WXRadii *radii = borderRect.radii;
     BOOL hasBorderRadius = [radii hasBorderRadius];
-    return (!hasBorderRadius) && _opacity == 1.0 && CGColorGetAlpha(self.styleBackgroundColor.CGColor) == 1.0 && [self _needsDrawBorder];
+    UIColor* currentBgColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    return (!hasBorderRadius) && _opacity == 1.0 && CGColorGetAlpha(currentBgColor.CGColor) == 1.0 && [self _needsDrawBorder];
 }
 
 - (CAShapeLayer *)drawBorderRadiusMaskLayer:(CGRect)rect
diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
index ad181ac..f630f02 100644
--- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
+++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
@@ -29,6 +29,7 @@
 #import "WXNavigationDefaultImpl.h"
 #import "WXURLRewriteDefaultImpl.h"
 #import "WXJSFrameworkLoadDefaultImpl.h"
+#import "WXDarkThemeDefaultImpl.h"
 
 #import "WXSDKManager.h"
 #import "WXSDKError.h"
@@ -205,6 +206,7 @@
     [self registerHandler:[WXNavigationDefaultImpl new] withProtocol:@protocol(WXNavigationProtocol)];
     [self registerHandler:[WXURLRewriteDefaultImpl new] withProtocol:@protocol(WXURLRewriteProtocol)];
     [self registerHandler:[WXJSFrameworkLoadDefaultImpl new] withProtocol:@protocol(WXJSFrameworkLoadProtocol)];
+    [self registerHandler:[WXDarkThemeDefaultImpl new] withProtocol:@protocol(WXDarkThemeProtocol)];
 }
 
 + (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h
similarity index 71%
rename from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
rename to ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h
index 1875e79..cba2971 100644
--- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
+++ b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h
@@ -18,12 +18,15 @@
  */
 
 #import <Foundation/Foundation.h>
+#import <WeexSDK/WXDarkThemeProtocol.h>
 
-@protocol WXDestroyProtocol <NSObject>
+NS_ASSUME_NONNULL_BEGIN
 
-/**
- *  @abstract execute unload function before dealloc
+/* By default, this implementation class do basic invert of UIColor of RGBA color space.
+ You should implementation your own handler to better handle dark theme in your application.
  */
-- (void)unload;
+@interface WXDarkThemeDefaultImpl : NSObject <WXDarkThemeProtocol>
 
 @end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m
similarity index 60%
copy from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
copy to ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m
index 1875e79..0717392 100644
--- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
+++ b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m
@@ -17,13 +17,22 @@
  * under the License.
  */
 
-#import <Foundation/Foundation.h>
+#import "WXDarkThemeDefaultImpl.h"
 
-@protocol WXDestroyProtocol <NSObject>
+@implementation WXDarkThemeDefaultImpl
 
-/**
- *  @abstract execute unload function before dealloc
- */
-- (void)unload;
+- (void)configureView:(UIView *)view ofComponent:(WXComponent *)component
+{
+    // Nothing
+}
+
+- (UIColor *_Nullable)getInvertedColorFor:(UIColor *_Nonnull)color ofScene:(WXColorScene)scene withDefault:(UIColor *_Nullable)defaultColor
+{
+    CGFloat red, blue, green, alpha;
+    if ([color getRed:&red green:&green blue:&blue alpha:&alpha]) {
+        return [UIColor colorWithRed:1 - red green:1 - green blue:1 - blue alpha:alpha];
+    }
+    return color;
+}
 
 @end
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm
index f4b29a8..b8f205c 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm
@@ -260,6 +260,7 @@
     WXAssert(_rootComponent == nil, @"Create body is invoked twice.");
     
     _rootComponent = [self _buildComponent:ref type:type supercomponent:nil styles:styles attributes:attributes events:events renderObject:renderObject];
+    _rootComponent.invertForDarkTheme = YES;
     
     CGSize size = _weexInstance.frame.size;
     [WXCoreBridge setDefaultDimensionIntoRoot:_weexInstance.instanceId
@@ -327,6 +328,11 @@
         }
     }
     
+    // Not explicitly declare "invertForDarkTheme", inherit
+    if (attributes[@"invertForDarkTheme"] == nil) {
+        component.invertForDarkTheme = supercomponent.invertForDarkTheme;
+    }
+    
 #ifdef DEBUG
     WXLogDebug(@"flexLayout -> _recursivelyAddComponent : super:(%@,%@):[%f,%f] ,child:(%@,%@):[%f,%f],childClass:%@",
                supercomponent.type,
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.h b/ios/sdk/WeexSDK/Sources/Model/WXComponent.h
index 0528b1d..4c31a95 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.h
@@ -32,6 +32,12 @@
     WXComponentUpdateStylesCallback
 } WXComponentCallbackType;
 
+typedef enum : NSUInteger {
+    WXColorSceneBackground,
+    WXColorSceneText,
+    WXColorSceneUnknown,
+} WXColorScene;
+
 /**
  * @abstract the component callback , result can be string or dictionary.
  * @discussion callback data to js, the id of callback function will be removed to save memory.
@@ -364,6 +370,8 @@
 /// @name Display
 ///--------------------------------------
 
+@property (nonatomic, assign) BOOL invertForDarkTheme;
+
 @property (nonatomic, assign) WXDisplayType displayType;
 
 /**
@@ -379,6 +387,16 @@
 - (BOOL)needsDrawRect;
 
 /**
+ * @abstract Fired on instance theme did changed.
+ */
+- (void)themeDidChange:(NSString*)theme;
+
+/**
+ * @abstract Hint used for better do color invert in dark mode.
+ */
+- (WXColorScene)colorSceneType;
+
+/**
  * @abstract Draws the component’s image within the passed-in rectangle.
  * @parameter rect The rectangle which is the entire visible bounds of your component. 
  * @return A UIImage containing the contents of the current bitmap graphics context.
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm
index 3235dbd..16de3c6 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm
@@ -42,6 +42,7 @@
 #import "WXSDKInstance_performance.h"
 #import "WXComponent_performance.h"
 #import "WXCoreBridge.h"
+#import "WXDarkThemeProtocol.h"
 
 #pragma clang diagnostic ignored "-Wincomplete-implementation"
 #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
@@ -71,6 +72,7 @@
 
 @synthesize transform = _transform;
 @synthesize styleBackgroundColor = _styleBackgroundColor;
+@synthesize darkThemeBackgroundColor = _darkThemeBackgroundColor;
 
 #pragma mark Life Cycle
 
@@ -104,6 +106,7 @@
         _eventPenetrationEnabled = NO;
         _accessibilityHintContent = nil;
         _cancelsTouchesInView = YES;
+        _invertForDarkTheme = NO;
         
         _async = NO;
         
@@ -122,6 +125,10 @@
             }
         }
         
+        if (attributes[@"invertForDarkTheme"]) {
+            _invertForDarkTheme = [WXConvert BOOL:attributes[@"invertForDarkTheme"]];
+        }
+        
         if (attributes[@"userInteractionEnabled"]) {
             _userInteractionEnabled = [WXConvert BOOL:attributes[@"userInteractionEnabled"]];
         }
@@ -380,6 +387,10 @@
         [self viewWillLoad];
         
         _view = [self loadView];
+        
+        // Provide a chance for dark theme handler to process the view
+        [[WXSDKInstance darkThemeColorHandler] configureView:_view ofComponent:self];
+        
 #ifdef DEBUG
         WXLogDebug(@"flexLayout -> loadView:addr-(%p),componentRef-(%@)",_view,self.ref);
 #endif
@@ -388,11 +399,19 @@
         _view.hidden = _visibility == WXVisibilityShow ? NO : YES;
         _view.clipsToBounds = _clipToBounds;
         if (![self _needsDrawBorder]) {
-            _layer.borderColor = _borderTopColor.CGColor;
+            _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:_invertForDarkTheme scene:[self colorSceneType]].CGColor;
             _layer.borderWidth = _borderTopWidth;
             [self _resetNativeBorderRadius];
             _layer.opacity = _opacity;
-            _view.backgroundColor = self.styleBackgroundColor;
+            
+            /* Also set background color to view to fix that problem that system may
+             set dynamic color to UITableView. Without these codes, event if we set
+             clear color to layer, the table view could not be transparent. */
+            UIColor* choosedColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:_invertForDarkTheme scene:[self colorSceneType]];
+            if (choosedColor == [UIColor clearColor]) {
+                _view.backgroundColor = choosedColor;
+            }
+            _layer.backgroundColor = choosedColor.CGColor;
         }
 
         if (_backgroundImage) {
@@ -845,7 +864,11 @@
     if (CGRectEqualToRect(self.view.frame, CGRectZero)) {
         return;
     }
-    NSDictionary * linearGradient = [WXUtility linearGradientWithBackgroundImage:_backgroundImage];
+    
+    BOOL isDark = [self.weexInstance isDarkTheme];
+    NSString* styleValue = isDark ? (_darkThemeBackgroundImage ?: _backgroundImage) : _backgroundImage;
+    
+    NSDictionary * linearGradient = [WXUtility linearGradientWithBackgroundImage:styleValue];
     if (!linearGradient) {
         return ;
     }
@@ -854,6 +877,7 @@
     dispatch_async(dispatch_get_main_queue(), ^{
         __strong typeof(self) strongSelf = weakSelf;
         if(strongSelf) {
+            // No need to auto-invert linear-gradient colors. We only allows using 'dark-theme-background-image' style.
             UIColor * startColor = (UIColor*)linearGradient[@"startColor"];
             UIColor * endColor = (UIColor*)linearGradient[@"endColor"];
             CAGradientLayer * gradientLayer = [WXUtility gradientLayerFromColors:@[startColor, endColor] locations:nil frame:strongSelf.view.bounds gradientType:(WXGradientType)[linearGradient[@"gradientType"] integerValue]];
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h
index c6cf521..e2df9fb 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h
@@ -26,6 +26,8 @@
 #import <WeexSDK/WXApmForInstance.h>
 #import <WeexSDK/WXComponentManager.h>
 
+@protocol WXDarkThemeProtocol;
+
 NS_ASSUME_NONNULL_BEGIN
 
 extern NSString *const bundleUrlOptionKey;
@@ -447,6 +449,32 @@
  */
 + (NSDictionary*)lastPageInfo;
 
+#pragma mark - Theme Support
+
+/**
+ Handler for handling color invert.
+
+ @return Handler instance.
+ */
++ (id<WXDarkThemeProtocol>)darkThemeColorHandler;
+
+/**
+ Return true if current is dark theme for this instance.
+ */
+- (BOOL)isDarkTheme;
+
+/**
+ Get/set interface style of current instance.
+ */
+- (NSString*)currentThemeName;
+- (void)setCurrentThemeName:(NSString*)name;
+
+/**
+ Choose final color between original color and dark-mode one.
+ Also considering invert.
+ */
+- (UIColor*)chooseColor:(UIColor*)originalColor darkThemeColor:(UIColor*)darkColor invert:(BOOL)invert scene:(WXColorScene)scene;
+
 /** 
  * Deprecated 
  */
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
index 7a22748..a0af7d7 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
@@ -51,7 +51,8 @@
 #import "WXPageEventNotifyEvent.h"
 #import "WXConvertUtility.h"
 #import "WXCoreBridge.h"
-#import <WeexSDK/WXDataRenderHandler.h>
+#import "WXDataRenderHandler.h"
+#import "WXDarkThemeProtocol.h"
 
 #define WEEX_LITE_URL_SUFFIX           @"wlasm"
 #define WEEX_RENDER_TYPE_PLATFORM       @"platform"
@@ -102,6 +103,7 @@
 {
     self = [super init];
     if (self) {
+        self.themeName = [WXUtility isSystemInDarkTheme] ? @"dark" : @"light";
         _renderType = renderType;
         _appearState = YES;
         
@@ -538,9 +540,6 @@
     if (!self.userInfo[@"jsMainBundleStringContentLength"]) {
         self.userInfo[@"jsMainBundleStringContentLength"] = @([mainBundleString length]);
     }
-    if (!self.userInfo[@"jsMainBundleStringContentLength"]) {
-        self.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:mainBundleString];
-    }
     
     id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)];
     if ([pageEvent respondsToSelector:@selector(pageStart:)]) {
@@ -1172,6 +1171,84 @@
     return result;
 }
 
++ (id<WXDarkThemeProtocol>)darkThemeColorHandler
+{
+    static id<WXDarkThemeProtocol> colorHandler;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        colorHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXDarkThemeProtocol)];
+    });
+    return colorHandler;
+}
+
+- (NSString*)currentThemeName
+{
+    return self.themeName;
+}
+
+- (BOOL)isDarkTheme
+{
+    return [self.themeName isEqualToString:@"dark"];
+}
+
+- (void)setCurrentThemeName:(NSString*)name
+{
+    if (name && ![name isEqualToString:self.themeName]) {
+        self.themeName = name;
+        
+        // Recursively visit all components and notify that theme had changed.
+        __weak WXSDKInstance* weakSelf = self;
+        WXPerformBlockOnComponentThread(^{
+            __strong WXSDKInstance* strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
+            
+            if (!strongSelf->_componentManager.isValid) {
+                return;
+            }
+            
+            [strongSelf->_componentManager enumerateComponentsUsingBlock:^(WXComponent * _Nonnull component, BOOL * _Nonnull stop) {
+                __weak WXComponent* wcomp = component;
+                WXPerformBlockOnMainThread(^{
+                    __strong WXComponent* scomp = wcomp;
+                    if (scomp) {
+                        [scomp themeDidChange:name];
+                    }
+                });
+            }];
+        });
+        
+        [[WXSDKManager bridgeMgr] fireEvent:_instanceId
+                                        ref:WX_SDK_ROOT_REF
+                                       type:@"themechanged"
+                                     params:@{@"theme": self.themeName?:@"light"}
+                                 domChanges:nil];
+    }
+}
+
+- (UIColor*)chooseColor:(UIColor*)originalColor darkThemeColor:(UIColor*)darkColor invert:(BOOL)invert scene:(WXColorScene)scene
+{
+    if ([self isDarkTheme]) {
+        if (darkColor) {
+            return darkColor;
+        }
+        else if (invert) {
+            // Invert originalColor
+            if (originalColor == [UIColor clearColor]) {
+                return originalColor;
+            }
+            return [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:originalColor ofScene:scene withDefault:originalColor];
+        }
+        else {
+            return originalColor;
+        }
+    }
+    else {
+        return originalColor;
+    }
+}
+
 @end
 
 @implementation WXSDKInstance (Deprecated)
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
index 25680da..852275d 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
@@ -39,6 +39,8 @@
 @property (nonatomic, strong) NSString *createInstanceContextResult;
 @property (nonatomic, strong) NSString *executeRaxApiResult;
 
+@property (atomic, strong) NSString* themeName;
+
 - (void)addModuleEventObservers:(NSString*)event callback:(NSString*)callbackId option:(NSDictionary*)option moduleClassName:(NSString*)moduleClassName;
 - (void)_addModuleEventObserversWithModuleMethod:(WXModuleMethod*)method;
 - (void)removeModuleEventObserver:(NSString*)event moduleClassName:(NSString*)moduleClassName;
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m
index 8ee1591..f75728a 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m
+++ b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m
@@ -26,6 +26,7 @@
 #import "WXLength.h"
 #import "WXTransition.h"
 #import "WXComponent+Layout.h"
+#import "WXDarkThemeProtocol.h"
 
 @interface WXAnimationInfo : NSObject<NSCopying>
 
@@ -207,7 +208,30 @@
     }
     CAMediaTimingFunction *timingFunction = [WXConvert CAMediaTimingFunction:args[@"timingFunction"]];
     NSDictionary *styles = args[@"styles"];
+    NSDictionary* componentRawStyles = target.styles;
+    
+    BOOL isDarkTheme = [target.weexInstance isDarkTheme];
+    BOOL updatingDarkThemeBackgroundColor = styles[@"darkThemeBackgroundColor"] != nil;
+    
     for (NSString *property in styles) {
+        if ([property isEqualToString:@"backgroundColor"]) {
+            if (isDarkTheme && (updatingDarkThemeBackgroundColor ||
+                                componentRawStyles[@"darkThemeBackgroundColor"] != nil)) {
+                /* Updating "darkThemeBackgroundColor" in dark mode,
+                 or this component has dark bg color explicitly defined in styels.
+                 We ignore transition animation for "backgroundColor" */
+                continue;
+            }
+        }
+        else if ([property isEqualToString:@"darkThemeBackgroundColor"]) {
+            if (!isDarkTheme || componentRawStyles[@"darkThemeBackgroundColor"] == nil) {
+                /* Do not do animation for "darkThemeBackgroundColor" in light mode.
+                 Or there is no dark bg color explicitly defined in styles.
+                 */
+                continue;
+            }
+        }
+        
         WXAnimationInfo *info = [WXAnimationInfo new];
         info.duration = duration;
         info.delay = delay;
@@ -287,10 +311,17 @@
                 [infos addObject:newInfo];
             }
             target.transform = wxTransform;
-        } else if ([property isEqualToString:@"backgroundColor"]) {
+        } else if ([property isEqualToString:@"backgroundColor"] ||
+                   [property isEqualToString:@"darkThemeBackgroundColor"]) {
             info.propertyName = @"backgroundColor";
             info.fromValue = (__bridge id)(layer.backgroundColor);
-            info.toValue = (__bridge id)[WXConvert CGColor:value];
+            UIColor* toColor = [WXConvert UIColor:value];
+            if ([target.weexInstance isDarkTheme] && target.invertForDarkTheme &&
+                [property isEqualToString:@"backgroundColor"]) {
+                // Invert color
+                toColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:toColor ofScene:[target colorSceneType] withDefault:toColor];
+            }
+            info.toValue = (__bridge id)([toColor CGColor]);
             [infos addObject:info];
         } else if ([property isEqualToString:@"opacity"]) {
             info.propertyName = @"opacity";
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm b/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm
index 8bb616d..b597232 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm
+++ b/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm
@@ -30,6 +30,7 @@
 #import "WXAssert.h"
 #import "WXSDKInstance_private.h"
 #import "WXLength.h"
+#import "WXDarkThemeProtocol.h"
 
 @implementation WXTransitionInfo
 @end
@@ -82,6 +83,7 @@
                                                            @"bottom": @(WXTransitionOptionsBottom),
                                                            @"top": @(WXTransitionOptionsTop),
                                                            @"backgroundColor": @(WXTransitionOptionsBackgroundColor),
+                                                           @"darkThemeBackgroundColor": @(WXTransitionOptionsBackgroundColor),
                                                            @"transform": @(WXTransitionOptionsTransform),
                                                            @"opacity": @(WXTransitionOptionsOpacity)
                                                            };
@@ -106,24 +108,55 @@
     _filterStyles = _filterStyles ?:[NSMutableDictionary new];
     _oldFilterStyles = _oldFilterStyles ?: [NSMutableDictionary new];
     NSMutableDictionary *futileStyles = [NSMutableDictionary new];
+    NSDictionary* componentRawStyles = targetComponent.styles;
+    
+    BOOL isDarkTheme = [targetComponent.weexInstance isDarkTheme];
+    BOOL updatingDarkThemeBackgroundColor = styles[@"darkThemeBackgroundColor"] != nil;
     
     for (NSString *key in styles) {
         if (self.transitionOptions & [self transitionOptionsFromString:key]) {
+            if ([key isEqualToString:@"backgroundColor"]) {
+                if (isDarkTheme && (updatingDarkThemeBackgroundColor ||
+                                    componentRawStyles[@"darkThemeBackgroundColor"] != nil)) {
+                    /* Updating "darkThemeBackgroundColor" in dark mode,
+                     or this component has dark bg color explicitly defined in styels.
+                     We ignore transition animation for "backgroundColor" */
+                    [futileStyles setObject:styles[key] forKey:key];
+                    continue;
+                }
+            }
+            else if ([key isEqualToString:@"darkThemeBackgroundColor"]) {
+                if (!isDarkTheme || componentRawStyles[@"darkThemeBackgroundColor"] == nil) {
+                    /* Do not do animation for "darkThemeBackgroundColor" in light mode.
+                     Or there is no dark bg color explicitly defined in styles.
+                     */
+                    [futileStyles setObject:styles[key] forKey:key];
+                    continue;
+                }
+            }
+            
             [_filterStyles setObject:styles[key] forKey:key];
             if (![key isEqualToString:@"transform"]) {
                 if (!isRunning) {
-                    /* style value may not be in component.styles, so we must get
-                     value from layout and convert it to style value. */
-                    id styleValue = targetComponent.styles[key];
-                    if (styleValue == nil) {
+                    // Get animation 'from' value from raw styles.
+                    id styleValue = componentRawStyles[key];
+                    if ([key isEqualToString:@"backgroundColor"] ||
+                        [key isEqualToString:@"darkThemeBackgroundColor"]) {
+                        if (styleValue == nil) {
+                            // background color is transparent by default.
+                            styleValue = @"transparent";
+                        }
+                    }
+                    else if (styleValue == nil) {
+                        /* Flex styles may not be in component.styles, so we must get
+                         value from layout and convert it to style value. */
                         styleValue = [targetComponent convertLayoutValueToStyleValue:key];
                     }
                     [_oldFilterStyles setObject:styleValue forKey:key];
                 }
             }
         }
-        else
-        {
+        else {
             [futileStyles setObject:styles[key] forKey:key];
         }
     }
@@ -131,7 +164,7 @@
 
     _targetComponent = targetComponent;
     NSMutableDictionary *componentStyles = [NSMutableDictionary dictionaryWithDictionary:styles];
-    [componentStyles addEntriesFromDictionary:targetComponent.styles];
+    [componentStyles addEntriesFromDictionary:componentRawStyles];
 
     _transitionDuration = componentStyles[kWXTransitionDuration] ? [WXConvert CGFloat:componentStyles[kWXTransitionDuration]] : 0;
     _transitionDelay = componentStyles[kWXTransitionDelay] ? [WXConvert CGFloat:componentStyles[kWXTransitionDelay]] : 0;
@@ -200,10 +233,21 @@
         if (!_propertyArray) {
             _propertyArray = [NSMutableArray new];
         }
-        if ([singleProperty isEqualToString:@"backgroundColor"]) {
+        if ([singleProperty isEqualToString:@"backgroundColor"] ||
+            [singleProperty isEqualToString:@"darkThemeBackgroundColor"]) {
+            UIColor* fromColor = [WXConvert UIColor:_oldFilterStyles[singleProperty]];
+            UIColor* toColor = [WXConvert UIColor:_filterStyles[singleProperty]];
+            if ([_targetComponent.weexInstance isDarkTheme] &&
+                _targetComponent.invertForDarkTheme &&
+                [singleProperty isEqualToString:@"backgroundColor"]) {
+                // Invert color
+                fromColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:fromColor ofScene:[_targetComponent colorSceneType] withDefault:fromColor];
+                toColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:toColor ofScene:[_targetComponent colorSceneType] withDefault:toColor];
+            }
+            
             WXTransitionInfo *info = [WXTransitionInfo new];
-            info.fromValue = [self _dealWithColor:[WXConvert UIColor:_oldFilterStyles[singleProperty]]];
-            info.toValue = [self _dealWithColor:[WXConvert UIColor:_filterStyles[singleProperty]]];
+            info.fromValue = [self _dealWithColor:fromColor];
+            info.toValue = [self _dealWithColor:toColor];
             info.perValue = [self _calculatePerColorRGB1:info.toValue RGB2:info.fromValue];
             info.propertyName = singleProperty;
             [_propertyArray addObject:info];
@@ -337,6 +381,7 @@
                                @([info.fromValue[3] floatValue] + [info.perValue[3] floatValue] * per)];
             UIColor *color = [UIColor colorWithRed:[array[0] floatValue] green:[array[1] floatValue] blue:[array[2] floatValue] alpha:[array[3] floatValue]];
             WXPerformBlockOnMainThread(^{
+                // Here we do not need to consider about dark mode.
                 _targetComponent.view.backgroundColor = color;
                 [_targetComponent.view setNeedsDisplay];
             });
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h b/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h
new file mode 100644
index 0000000..c443879
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#import <WeexSDK/WXModuleProtocol.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol WXDarkThemeProtocol <WXModuleProtocol>
+
+/**
+After any view of Weex component is created. Callback dark theme handler to provide a
+ chance to configure the view.
+*/
+- (void)configureView:(UIView*_Nonnull)view ofComponent:(WXComponent*_Nonnull)component;
+
+/**
+ Get inverted color in dark mode for input color with scene hint.
+
+ @param color Input color.
+ @param scene Scene indicating the color usage.
+ @param defaultColor If no inverted one matches, return the default color.
+ @return Inverted color.
+ */
+- (UIColor *_Nullable)getInvertedColorFor:(UIColor *_Nonnull)color ofScene:(WXColorScene)scene withDefault:(UIColor *_Nullable)defaultColor;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
index 61eeccd..4addab0 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
@@ -131,6 +131,14 @@
 + (void)performBlock:(void (^_Nonnull)(void))block onThread:(NSThread *_Nonnull)thread;
 
 /**
+ * @abstract Check if system is in dark mode.
+ *
+ * @return Boolean
+ *
+ */
++ (BOOL)isSystemInDarkTheme;
+
+/**
  * @abstract Returns the environment of current application, you can get some necessary properties such as appVersion、sdkVersion、appName etc.
  *
  * @return A dictionary object which contains these properties.
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
index 6ec4223..6d7136b 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
@@ -163,6 +163,22 @@
     return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:UISemanticContentAttributeUnspecified] == UIUserInterfaceLayoutDirectionRightToLeft ? WXLayoutDirectionRTL : WXLayoutDirectionLTR;
 }
 
++ (BOOL)isSystemInDarkTheme
+{
+    if (@available(iOS 13.0, *)) {
+        __block BOOL result = NO;
+        WXPerformBlockSyncOnMainThread(^{
+#ifdef __IPHONE_13_0
+            if ([UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark) {
+                result = YES;
+            }
+#endif
+        });
+        return result;
+    }
+    return NO;
+}
+
 + (NSDictionary *)getEnvironment
 {
     NSString *platform = @"iOS";
@@ -187,7 +203,8 @@
                                     @"deviceWidth":@(deviceWidth * scale),
                                     @"deviceHeight":@(deviceHeight * scale),
                                     @"scale":@(scale),
-                                    @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr"
+                                    @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr",
+                                    @"theme": [self isSystemInDarkTheme] ? @"dark" : @"light"
                                 }];
     
     if ([[[UIDevice currentDevice] systemVersion] integerValue] >= 11) {
diff --git a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm
index ce252d0..accb21a 100644
--- a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm
+++ b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm
@@ -67,6 +67,14 @@
     }\
 } while(0);
 
+#define WX_BOARD_RADIUS_DARK_THEME_COLOR_RESET_ALL(key)\
+do {\
+    if (styles && [styles containsObject:@#key]) {\
+        _darkThemeBorderTopColor = _darkThemeBorderLeftColor = _darkThemeBorderRightColor = _darkThemeBorderBottomColor = [UIColor blackColor];\
+        [self setNeedsDisplay];\
+    }\
+} while(0);
+
 #define WX_BOARD_COLOR_RESET(key)\
 do {\
     if (styles && [styles containsObject:@#key]) {\
@@ -175,7 +183,11 @@
 - (void)_initViewPropertyWithStyles:(NSDictionary *)styles
 {
     self.styleBackgroundColor = styles[@"backgroundColor"] ? [WXConvert UIColor:styles[@"backgroundColor"]] : [UIColor clearColor];
+    if (styles[@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]];
+    }
     _backgroundImage = styles[@"backgroundImage"] ? [WXConvert NSString:styles[@"backgroundImage"]]: nil;
+    _darkThemeBackgroundImage = styles[@"darkThemeBackgroundImage"] ? [WXConvert NSString:styles[@"darkThemeBackgroundImage"]] : nil;
     _opacity = styles[@"opacity"] ? [WXConvert CGFloat:styles[@"opacity"]] : 1.0;
     _clipToBounds = styles[@"overflow"] ? [WXConvert WXClipType:styles[@"overflow"]] : NO;
     _visibility = styles[@"visibility"] ? [WXConvert WXVisibility:styles[@"visibility"]] : WXVisibilityShow;
@@ -195,6 +207,9 @@
     if (styles[@"backgroundColor"]) {
         self.styleBackgroundColor = [WXConvert UIColor:styles[@"backgroundColor"]];
     }
+    if (styles[@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]];
+    }
     if (styles[@"opacity"]) {
         _opacity = [WXConvert CGFloat:styles[@"opacity"]];
     }
@@ -215,9 +230,21 @@
         [self setNeedsDisplay];
     }
     
+    if (styles[@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]];
+        [self setNeedsDisplay];
+    }
+    
     if (styles[@"backgroundImage"]) {
-        _backgroundImage = styles[@"backgroundImage"] ? [WXConvert NSString:styles[@"backgroundImage"]]: nil;
-        if (_backgroundImage) {
+        _backgroundImage = [WXConvert NSString:styles[@"backgroundImage"]];
+    }
+    
+    if (styles[@"darkThemeBackgroundImage"]) {
+        _darkThemeBackgroundImage = [WXConvert NSString:styles[@"darkThemeBackgroundImage"]];
+    }
+    
+    if (styles[@"backgroundImage"] || styles[@"darkThemeBackgroundImage"]) {
+        if (_backgroundImage || _darkThemeBackgroundImage) {
             [self setGradientLayer];
         }
     }
@@ -302,6 +329,11 @@
     WX_BOARD_COLOR_RESET(borderLeftColor);
     WX_BOARD_COLOR_RESET(borderRightColor);
     WX_BOARD_COLOR_RESET(borderBottomColor);
+    WX_BOARD_RADIUS_DARK_THEME_COLOR_RESET_ALL(darkThemeBorderColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderTopColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderLeftColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderRightColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderBottomColor);
 }
 
 - (void)_resetStyles:(NSArray *)styles
@@ -310,6 +342,11 @@
         self.styleBackgroundColor = [UIColor clearColor];
         [self setNeedsDisplay];
     }
+    if (styles && [styles containsObject:@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = nil;
+        [self setNeedsDisplay];
+    }
+    
     if (styles && [styles containsObject:@"boxShadow"]) {
         _lastBoxShadow = _boxShadow;
         _boxShadow = nil;
@@ -317,6 +354,11 @@
     }
     if (styles && [styles containsObject:@"backgroundImage"]) {
         _backgroundImage = nil;
+    }
+    if (styles && [styles containsObject:@"darkThemeBackgroundImage"]) {
+        _darkThemeBackgroundImage = nil;
+    }
+    if (styles && ([styles containsObject:@"backgroundImage"] || [styles containsObject:@"darkThemeBackgroundImage"])) {
         [self setGradientLayer];
     }
     
diff --git a/ios/sdk/WeexSDK/Sources/View/WXRootView.m b/ios/sdk/WeexSDK/Sources/View/WXRootView.m
index 3cc8a36..8b2414b 100644
--- a/ios/sdk/WeexSDK/Sources/View/WXRootView.m
+++ b/ios/sdk/WeexSDK/Sources/View/WXRootView.m
@@ -23,6 +23,10 @@
 #import "WXSDKEngine.h"
 
 @interface WXRootView()
+{
+    BOOL _hasFirstTraitCollectionChange;
+    BOOL _allowFirstTraitCollectionChange;
+}
 
 @property (nonatomic, assign) BOOL mHasEvent;
 
@@ -59,4 +63,36 @@
     return _mHasEvent;
 }
 
+- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
+{
+    [super traitCollectionDidChange:previousTraitCollection];
+    
+    if (@available(iOS 13.0, *)) {
+        // When entering background system may call back change twice.. We ignore the first one.
+        UIUserInterfaceStyle currentStyle = self.traitCollection.userInterfaceStyle;
+        if (currentStyle != previousTraitCollection.userInterfaceStyle) {
+            if (_hasFirstTraitCollectionChange) {
+                _allowFirstTraitCollectionChange = NO;
+                [self.instance setCurrentThemeName:currentStyle == UIUserInterfaceStyleDark ? @"dark" : @"light"];
+            }
+            else {
+                __weak WXRootView* weakSelf = self;
+                _hasFirstTraitCollectionChange = YES;
+                _allowFirstTraitCollectionChange = YES;
+                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+                    __strong WXRootView* strongSelf = weakSelf;
+                    if (strongSelf) {
+                        if (strongSelf->_allowFirstTraitCollectionChange) {
+                            if (strongSelf.instance) {
+                                [strongSelf.instance setCurrentThemeName:currentStyle == UIUserInterfaceStyleDark ? @"dark" : @"light"];
+                            }
+                        }
+                        strongSelf->_hasFirstTraitCollectionChange = NO;
+                    }
+                });
+            }
+        }
+    }
+}
+
 @end
diff --git a/ios/sdk/WeexSDK/Sources/WeexSDK.h b/ios/sdk/WeexSDK/Sources/WeexSDK.h
index 8e5f6f4..05dbe21 100644
--- a/ios/sdk/WeexSDK/Sources/WeexSDK.h
+++ b/ios/sdk/WeexSDK/Sources/WeexSDK.h
@@ -73,6 +73,7 @@
 #import <WeexSDK/WXDefine.h>
 #import <WeexSDK/WXDebugTool.h>
 #import <WeexSDK/WXDataRenderHandler.h>
+#import <WeexSDK/WXDarkThemeProtocol.h>
 #import <WeexSDK/WXConvertUtility.h>
 #import <WeexSDK/WXConvert.h>
 #import <WeexSDK/WXConfigCenterProtocol.h>